PolderAPI

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

GET/api/v1/inboxscope inbox:read

Your 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

GET/api/v1/inbox/unread-countscope inbox:read

The cheap call for a badge: one count instead of a page of notifications.

{ "data": { "unread": 3 } }

Mark everything read

POST/api/v1/inbox/read-allscope inbox:write

Marks 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

POST/api/v1/inbox/{notification}/readscope inbox:write

Without 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

POST/api/v1/inbox/{notification}/snoozescope inbox:write

Hides 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

POST/api/v1/inbox/{notification}/archivescope inbox:write

Takes 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

POST/api/v1/devicesscope inbox:write

The 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

DELETE/api/v1/devicesscope inbox:write

Stops 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).