Responses and errors
The shape of every response, the error envelope, error codes and what to do about each.
Responses
- One resource is the object itself:
GET /sources/{id}returns the source. - A list is
{ "items": [...], "nextCursor": "…" | null, "hasMore": true | false }. See Pagination. - A delete (and a few writes with nothing to say) is
204with no body. - Field names are the database's:
short_id,in_library,warc_status,bookmark_id. A source is abookmarkin field names; the URLs saysources. {id}on a source accepts its UUID or its short id (theabc123XYZin a share link), whichever you hold.- Booleans in a query string are the strings
trueandfalse. - People who keep their profile private stay private: when a response names
someone else (a source's owner, a comment's author), those fields are
nullfor a private person you are not connected to. Treat owner and user fields as nullable.
Errors
Every failure has the same body:
{ "error": { "code": "not_found", "message": "Source not found", "requestId": "0f56a8d6-…" } }code | HTTP | Meaning | What to do |
|---|---|---|---|
validation_failed | 400 | the request is malformed; details lists the problems (zod issues), or carries a reason | fix the request |
unsafe_url | 400 | the URL is not a public http(s) address (localhost, a private IP, file:…) | do not retry |
unauthorized | 401 | no credential, or a revoked, expired or unknown one | get a new token |
forbidden | 403 | authenticated but not allowed, including a read token trying to write | check the scope |
not_found | 404 | no such resource, or one you may not see | — |
conflict | 409 | it already exists (details.reason when there is more than one kind) | — |
rate_limited | 429 | a limit was hit; see Rate limits | wait Retry-After seconds when present |
internal | 500 | our bug | retry once; report the requestId |
Branch on code, and on details.reason where a route documents one (folder moves:
cycle, too_deep, parent_not_found, name_taken; tokens: too_many_tokens).
Never parse message
message is for people, and it can be translated. Code against code and
details.reason only.
Someone else's private row is a 404, not a 403: a 403 would confirm the id
exists.
URLs you send must be public. A URL whose host is localhost, a private or
link-local address is 400 unsafe_url, before anything is fetched. That applies to
saving a source, resolving URL metadata and subscribing to a feed.
Headers
Every response carries x-request-id (the same value as requestId; quote it when you
report a problem) and Cache-Control: no-store, except GET /openapi.json, which is
the same public document for everyone and is Cache-Control: public, max-age=300.
Responses to a personal access token also carry the rate-limit headers described in Rate limits.