Backlog API
Triage ingest
Send a report from a script, a form or a support tool into a team's triage queue with a per-team token.
Base URLhttps://backlog.polder.work
Triage is the queue where new reports wait for a person to accept, decline or route them. This endpoint is the way for a machine to put a report there: a form on your marketing site, a support tool's webhook, a script.
It is not part of /api/v1. It uses no personal API token and no scopes. The credential is an ingest token that belongs to one team (or one customer), and there is no user behind the request, so the issue it creates has no creator.
Get a token
An administrator mints a team token in Backlog under Settings → Feedback API, or on the server with php artisan triage:token ENG (the team key). The token looks like bti_<team id>_<secret>.
- It is shown once. Only a hash is stored.
- Making a new one replaces the old one: the previous token stops working at once.
- A token files into its own team's queue only.
A customer can have a token as well, btc_<customer id>_<secret>, made with php artisan customers:token <customer slug> --team=ENG. Reports sent with it land in the team the customer is set up to file into, are linked to that customer and are recorded as the customer's request. A customer token with no team set cannot be used (401).
Create a report
/ingest/triageCreates an issue in the team's triage queue with a pending triage status. It gets a normal identifier (ENG-312) from the team, the default state, and a triage_submitted activity. The team's triage badge updates, and Flows that start on a triage report are triggered. The issue stays out of the team's issue lists until a person accepts it.
Send the token as a bearer token. The body is JSON, or multipart/form-data when you attach files.
curl https://backlog.polder.work/ingest/triage \
-H "Authorization: Bearer $POLDER_INGEST_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Export button does nothing",
"description": "<p>Clicking Export on the invoices page shows no download.</p>",
"priority": 2,
"source": "website",
"reporter_name": "Ann Visser",
"reporter_email": "ann@example.com",
"external_url": "https://support.example.com/tickets/881",
"labels": ["bug", "Billing"],
"fields": { "Browser": "Firefox 131" }
}'
Body
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | yes | Max 255. |
description |
string | no | Rich text as HTML, max 20 000. It is sanitised like every other rich text. |
priority |
integer | no | 0 none, 1 urgent, 2 high, 3 medium, 4 low. |
source |
string | no | Max 40. Plain text for the "Via …" line on the triage card. Defaults to api. For a customer token the customer's name is used instead. |
reporter_name |
string | no | Max 255. Who wrote it in your system. Shown to the triager; it never logs anyone in and is not matched to a member. |
reporter_email |
string | no | Valid address, max 255. Same: shown only. |
external_url |
string | no | http or https only, max 2000. Where the report lives at the sender. |
labels |
string[] | no | Max 20 label names, case-insensitive. A name that matches no label of the workspace (or of this team) is skipped, not an error. Label groups cannot be used. |
fields |
object | no | Max 50. Custom field name to value. A name that matches no custom field of the team is skipped. A known field given a value it cannot hold is 422. |
attachments |
file[] | no | Max 10 files, multipart. Same types and the same 10 MB limit as an upload in the editor. One refused file refuses the whole report. |
The reporter and the link have no field of their own: they are appended to the description as "Reporter" and "Link" lines.
Everything happens in one transaction: if a custom field value or an attachment is refused, no half-filled issue is left in the queue.
Response 201. Note there is no data envelope here.
{ "id": 4120, "identifier": "ENG-312" }
Errors
| Status | Meaning |
|---|---|
401 |
The token is missing, malformed, unknown or wrong. This is one answer on purpose, so that the status cannot be used to find out which teams exist. It also covers a team without a token and a customer without a destination team. |
422 |
Validation. The body lists the failing fields (title, priority, external_url, …). |
429 |
More than 30 requests per minute from one IP address. The limit is per IP because that is the only key before the token is read; slow down and retry. |
There is no CSRF check on this route, and no idempotency key: sending the same report twice creates two issues.
Other ways into triage
The same queue also takes reports from:
- Public intake forms: a workspace publishes a form at
https://backlog.polder.work/forms/{workspace}/{form}, open to anyone with the link and optionally protected by a password. The form, not the URL, decides which team receives the submission. - Mail: a workspace connects a Lettermint inbound project and mail sent to its address is delivered to Backlog, which turns it into a triage report.
- People: anyone can send an issue to triage from inside the app.
Only this endpoint is meant for scripts. The form and mail routes are called by browsers and by Lettermint, and are not a public API.