PolderAPI

Backlog API

Workspace

Who you are, and the teams, states, people, labels, views and articles you need to know before you touch an issue.

Base URLhttps://backlog.polder.work

These endpoints answer the questions you have before you create or move an issue: which workspace ids may I use, which team keys exist, which workflow_state_id means "done", who can I assign it to.

Collections hang under a workspace (/api/v1/workspaces/{workspace}/…). Single resources are addressed flatly by id (/api/v1/members/{user}). A workspace you are not a member of answers 404, not 403.

Envelope and pagination

Every response puts the payload under data. Lists add meta and are paged:

{
  "data": [ … ],
  "meta": { "page": 1, "per_page": 50, "total": 214, "last_page": 5 }
}
Query parameter Description
page Page number, starting at 1.
per_page Page size. Default 50, minimum 1, maximum 200 (larger values are clamped, not refused).

Every list in this API uses this envelope, with the exception of the relations list under Issues, which is not paged. Deletes answer 204 with no body.

Note: A token narrows what you may use; it never widens what its owner may do. A missing scope is a 403, and so is a policy that says no. See Scopes.

Who am I

GET/api/v1/mescope none

The first call an integration makes. It needs no scope, so it works for any valid token and is the cheapest way to tell a broken token from a broken request.

curl https://backlog.polder.work/api/v1/me \
  -H "Authorization: Bearer $POLDER_TOKEN"
{
  "data": {
    "user": { "id": 1, "name": "Sanne", "email": "sanne@example.com", "isBot": false },
    "scopes": ["issues:read", "issues:write"],
    "workspace": null,
    "workspaces": [{ "id": 7, "name": "Studio", "slug": "studio" }]
  }
}
Field Description
user The token's owner.
scopes The scopes this token carries.
workspace The one workspace this token is confined to, or null. It is set for a token issued to a connected app (OAuth), which can reach nothing else (403 for any other workspace). null for a personal access token.
workspaces Every workspace the person belongs to, ordered by name. For an OAuth token, prefer workspace: it is the only one your requests will be allowed into.

Teams

GET/api/v1/workspaces/{workspace}/teamsscope workspace:read

The teams you can see, ordered by name. A private team you are not in is not listed, so its name does not leak.

curl https://backlog.polder.work/api/v1/workspaces/7/teams \
  -H "Authorization: Bearer $POLDER_TOKEN"
{
  "data": [
    {
      "id": 3, "key": "ENG", "name": "Engineering",
      "color": "#6366f1", "icon": "code", "logo_url": null, "is_private": false
    }
  ],
  "meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}

The key is what you pass as team when you create or filter issues. The id is what team_id parameters take.

Workflow states

GET/api/v1/workspaces/{workspace}/workflow-statesscope workspace:read

Every workflow column of every team you can see, ordered by team and then position. Fetch it once: moving an issue needs a workflow_state_id, and there is no other way to learn which id means "done" for a team.

{
  "data": [
    { "id": 21, "team_id": 3, "name": "In Progress", "type": "started", "color": "#f59e0b", "progress": 0.5, "position": 2 }
  ],
  "meta": { … }
}

type is one of backlog, unstarted, started, completed, canceled.

Tip: Switch on type, never on name. Teams rename their columns, and an integration keyed on "Done" breaks the day somebody calls it "Shipped".

Members

GET/api/v1/workspaces/{workspace}/membersscope workspace:read

The people in the workspace, ordered by name. This is where the ids for assignee_id, assignee_ids and reviewer_ids come from. Membership of a private team is not narrowed here, only the workspace.

Query parameter Description
q Case-insensitive substring match on name or email.
curl "https://backlog.polder.work/api/v1/workspaces/7/members?q=sanne" \
  -H "Authorization: Bearer $POLDER_TOKEN"
{
  "data": [{ "id": 1, "name": "Sanne", "email": "sanne@example.com", "isBot": false }],
  "meta": { … }
}

Note: The key is isBot in camelCase, unlike every other field in the API, which is snake_case.

A member

GET/api/v1/members/{user}scope workspace:read

One person by id. It only answers for people you share a workspace with; anyone else is a 404, so a token cannot turn arbitrary user ids into names and email addresses.

Labels

GET/api/v1/workspaces/{workspace}/labelsscope workspace:read

Workspace-wide and team labels together, ordered by name. Label groups are included (is_group: true).

Query parameter Description
team_id Only labels of that team plus the workspace-wide ones (team_id: null), because those apply to every team.
{
  "data": [
    { "id": 12, "name": "bug", "color": "#ef4444", "team_id": null, "group_id": null, "is_group": false }
  ],
  "meta": { … }
}

Create a label

POST/api/v1/workspaces/{workspace}/labelsscope workspace:write

Creates a label and answers 201 with it. Labels are workspace furniture, so this needs an administrator of the workspace (403 otherwise), the same bar as the settings screen.

Body

Field Type Required Description
name string yes Up to 60 characters.
color string yes Up to 20 characters, for example #ef4444.
team_id integer no A team in this workspace. Omit for a workspace-wide label.
group_id integer no A label in this workspace to nest it under.
curl https://backlog.polder.work/api/v1/workspaces/7/labels \
  -H "Authorization: Bearer $POLDER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "bug", "color": "#ef4444"}'
{ "data": { "id": 12, "name": "bug", "color": "#ef4444", "team_id": null, "group_id": null, "is_group": false } }

Failing validation answers 422. The label is never created as a group through the API (is_group is always false).

Saved views

A saved view is a stored set of filters. Its filters and display are handed over verbatim, and that is deliberately not a contract: the set of filterable properties moves with the product.

Important: Treat filters and display as opaque. Ask the view's issues for the result instead. The server runs the view through the same filter engine as the web list.

GET/api/v1/workspaces/{workspace}/saved-viewsscope workspace:read

The views you may see: the shared ones (shared: true) plus your own personal ones, in teams you can see. Personal views come first, then shared ones, each by position.

Query parameter Description
team_id Only views of that team. Workspace-wide views (no team) are not matched by this filter.
{
  "data": [
    {
      "id": 5, "workspace_id": 7, "team_id": 3, "name": "My open bugs",
      "icon": null, "color": null, "shared": false, "is_favourite": true, "position": 0,
      "filters": { … }, "display": { … },
      "created_at": "2026-08-01T09:00:00+00:00", "updated_at": "2026-08-02T10:30:00+00:00"
    }
  ],
  "meta": { … }
}

A saved view

GET/api/v1/saved-views/{view}scope workspace:read

One view. Somebody else's personal view, a view in a workspace you are not in and a view in a team you cannot see are all a 404 (or 403 for the team case), so ids do not confirm that they exist.

The issues in a saved view

GET/api/v1/saved-views/{view}/issuesscope workspace:read, issues:read

The view resolved to its issues, in the view's own ordering (display.ordering, falling back to the manual board order). Needs both scopes: what comes back is issues, and being allowed to read a view's definition is not the same as being allowed to read the work it selects.

Query parameter Description
team_id Required when the view is not tied to a team, refused when it is.
page, per_page See pagination.
Status When
422 The view has no team and you sent no team_id ("This view is not tied to a team; send team_id."), you sent a team_id for a view that has a team, or the team does not exist in the view's workspace.
403 You may not see that team.

Each item has the same shape as an issue from the issue list, including is_favorite.

curl "https://backlog.polder.work/api/v1/saved-views/5/issues?per_page=20" \
  -H "Authorization: Bearer $POLDER_TOKEN"

Knowledge base articles

Read-only through this API. An article's body is long-form HTML written in an editor, so the list leaves it out.

GET/api/v1/workspaces/{workspace}/knowledge-articlesscope documents:read

Published articles for everybody in the workspace, plus your own drafts. Newest updated_at first.

Query parameter Description
q Full-text search, up to 255 characters. Prefix-based, with no stemming: authent finds Authentication, running does not find run.
project_id Only articles of that project.
status draft or published.
page, per_page See pagination.
{
  "data": [
    {
      "id": 40, "workspace_id": 7, "project_id": null, "title": "Deploying to production",
      "status": "published",
      "creator": { "id": 1, "name": "Sanne", "email": "sanne@example.com", "isBot": false },
      "labels": [ … ],
      "verified_at": null,
      "created_at": "2026-07-01T08:00:00+00:00", "updated_at": "2026-07-02T08:00:00+00:00"
    }
  ],
  "meta": { … }
}

A role that may not read internal content (a guest) gets 403.

A knowledge base article

GET/api/v1/knowledge-articles/{article}scope documents:read

One article, the same fields as the list plus body (HTML). Writing articles is not part of the REST API; the MCP server has tools for it.