Backlog API
Inbox and devices
Read and tidy your own notifications, count the unread ones, and register a phone for push.
Base URLhttps://backlog.polder.work
The inbox holds what was addressed to you: assignments, mentions, comments, review requests, reminders and the like. It belongs to the owner of the token, so it is the one collection in this API that does not hang under a workspace. There is no shared inbox and nothing to address except a notification id.
Reading takes inbox:read; marking, snoozing, archiving and registering devices take inbox:write. See Scopes. The scope is its own family on purpose: a script that may read your issues has not been allowed to read what was said to you.
"In the inbox" means the same here as in the app: not archived, and not snoozed past its time.
The notification object
{
"id": 981,
"type": "mentioned",
"payload": { "…": "…" },
"read": false,
"read_at": null,
"snoozed_until": null,
"archived_at": null,
"actor": { "id": 5, "name": "Sanne", "email": "sanne@example.com", "isBot": false },
"issue": {
"id": 412,
"identifier": "ENG-12",
"title": "Printer is on fire",
"state": { "id": 3, "name": "In progress", "type": "started", "…": "…" }
},
"created_at": "2026-10-28T08:15:00+00:00"
}
| Field | Description |
|---|---|
type |
What happened, for example assigned, unassigned, commented, status_changed, priority_changed, mentioned, sub_issue_added, project_update, review_requested, reminder, pull_request_merged, sla_at_risk, sla_breached, customer_replied. New types can be added; do not fail on one you do not know. |
payload |
Extra data that depends on type. |
read |
Derived flag. read_at is the timestamp behind it, or null. |
actor |
Who caused it, or null (a reminder falling due has no actor). |
issue |
A trimmed copy (id, identifier, title, state) so a list does not need a follow-up request per row, or null. Switch on state.type, not state.name: teams rename their columns. |
List notifications
/api/v1/inboxYour notifications, newest first.
Query
| Parameter | Description |
|---|---|
unread |
1 returns only unread notifications. |
page, per_page |
Paging. per_page defaults to 50, at most 200. |
curl "https://backlog.polder.work/api/v1/inbox?unread=1" \
-H "Authorization: Bearer $POLDER_TOKEN"
{ "data": [ { "id": 981, "type": "mentioned", "read": false, "…": "…" } ], "meta": { "page": 1, "per_page": 50, "total": 3, "last_page": 1 } }
Count unread notifications
/api/v1/inbox/unread-countThe cheap call for a badge: one count instead of a page of notifications.
{ "data": { "unread": 3 } }
Mark everything read
/api/v1/inbox/read-allMarks every unread notification in your inbox as read. Snoozed and archived ones are not touched. Answers with how many changed.
{ "data": { "marked": 3 } }
Mark one read or unread
/api/v1/inbox/{notification}/readWithout a body this toggles, as the app does (people use "mark unread" to mean "deal with this later"). Send {"read": true} or {"read": false} to be explicit. Answers with the notification.
curl -X POST https://backlog.polder.work/api/v1/inbox/981/read \
-H "Authorization: Bearer $POLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"read": true}'
A notification that is not yours is a 404, not a 403: the id is not confirmed to exist. The same goes for snooze and archive.
Snooze a notification
/api/v1/inbox/{notification}/snoozeHides it from the inbox for 24 hours. There is no body and no way to choose the duration. Answers with the notification (snoozed_until set).
Archive a notification
/api/v1/inbox/{notification}/archiveTakes it out of the inbox. An unread notification is marked read on the way out, so a badge never keeps counting it. Answers with the notification.
Register a device
/api/v1/devicesThe mobile app tells Polder where to push your inbox. It uses inbox:write: a device is only somewhere to deliver your inbox, and a token that may archive your notifications gains nothing by also knowing where to ring.
| Field | Type | Required | Description |
|---|---|---|---|
token |
string | yes | An Expo push token, ExponentPushToken[…] or ExpoPushToken[…], max 255. Anything else is 422. |
platform |
string | yes | ios or android. |
The call is idempotent, and apps should make it on every launch because the token can change without notice. The token is unique: a phone that changes hands moves to whoever registers it last. Answers 201 the first time and 200 after that.
curl https://backlog.polder.work/api/v1/devices \
-H "Authorization: Bearer $POLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"token": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]", "platform": "ios"}'
{ "data": { "id": 17 } }
Remove a device
/api/v1/devicesStops pushes to a phone, on sign out. The token goes in the body, not the path.
curl -X DELETE https://backlog.polder.work/api/v1/devices \
-H "Authorization: Bearer $POLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"token": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]"}'
Answers 204. It only removes your own devices, and a token that is unknown (or someone else's) is not an error, so signing out of a phone that is already gone works. token is required (422).