You choose the IDs
Report IDs and file IDs are UUIDv4 values that you make. Make the ID one time, store it, and use the same ID for each retry.PUT /api/reports/{report_id}with the same ID never makes a second report.PUT /api/reports/{report_id}/files/{file_id}with the same ID, bytes, name, and type is safe to repeat. Different content with the same ID gives409 file_conflict.
Revisions
Each report has arevision. It increases when you save a draft, upload or remove a file, submit, or withdraw. It can increase by more than one in one call. A message does not change it.
- Send
revision: 0to create a draft. - Send the
revisionfrom the last response with each next change. - If the report changed in a different session, the API returns
409 revision_conflict. The body hascurrent, the stored report. Merge your change intocurrent, then send it again withcurrent.revision.
Safe actions
Page through reports
GET /api/reports returns reports in order of updated_at, newest first.
- Send the first request with no
cursor. - If
next_cursoris notnull, send it ascursorto get the next page. - Stop when
next_cursorisnull.
422 validation_error. Start again from the first page.
Poll for triage changes
There are no webhooks. Poll withupdated_since.
- Keep
since: the newestupdated_atthat you saw. Use theZform that the API returns, for example2026-10-01T12:00:00Z. - Send
GET /api/reports?updated_since=<since>. URL-encode the value. A raw+in a URL is a space. - Read all pages with
next_cursor. - Set
sinceto the newestupdated_aton the first page. - For each report with status
needs_info, read the thread and reply.
updated_at unless it starts a turn.
Payout progress does not change updated_at. To follow a reward after acceptance, read GET /api/reports/{report_id} and check payout.
Poll at most once each minute. Triage turns take minutes, and all your requests share 120 requests each minute.
Poll for reports that need a reply