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
/api/v1/meThe 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
/api/v1/workspaces/{workspace}/teamsThe 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
/api/v1/workspaces/{workspace}/workflow-statesEvery 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 onname. Teams rename their columns, and an integration keyed on "Done" breaks the day somebody calls it "Shipped".
Members
/api/v1/workspaces/{workspace}/membersThe 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
isBotin camelCase, unlike every other field in the API, which is snake_case.
A member
/api/v1/members/{user}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
/api/v1/workspaces/{workspace}/labelsWorkspace-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
/api/v1/workspaces/{workspace}/labelsCreates 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
filtersanddisplayas opaque. Ask the view's issues for the result instead. The server runs the view through the same filter engine as the web list.
/api/v1/workspaces/{workspace}/saved-viewsThe 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
/api/v1/saved-views/{view}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
/api/v1/saved-views/{view}/issuesThe 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.
/api/v1/workspaces/{workspace}/knowledge-articlesPublished 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
/api/v1/knowledge-articles/{article}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.