Documentation

Errors

Every failure, on every endpoint, comes back the same way:

{
  "message": "Invalid cursor \"page-2\"; pass the next_cursor value from a previous response.",
  "code": "invalid_cursor",
  "errors": { "cursor": ["Invalid cursor \"page-2\"; ..."] }
}
FieldNotes
messageFor a human. It may be reworded at any time — do not branch on it.
codeFor a program. Stable. This is the one to branch on.
errorsPer-field messages. Present on validation failures, absent otherwise.

The codes

CodeStatusMeans
validation_failed422A parameter was wrong. errors says which.
invalid_cursor422A cursor we did not issue. Start again without one.
scope_conflict422Mutually exclusive scopes together, e.g. both source and person.
not_found404No such slug.
unauthenticated401Missing, malformed or revoked key.
forbidden403Valid key, but its team may not touch this row.
no_plan402The team has no active plan.
rate_limited429Too many requests. Wait for Retry-After.

Which ones are worth retrying

Retry rate_limited, after the number of seconds in the Retry-After header.

Do not retry anything else. A 402 in particular looks like a throttle and is not one: the team has no plan, and retrying will not change that. Treating it as a rate limit is how a client ends up in a loop that never resolves.

404 tells you when you used the wrong kind of key

Passing a database id where a slug belongs is the most common way to mis-address this API, so the message says so explicitly rather than leaving you guessing:

{
  "message": "No post found with slug \"48213\". That looks like a numeric id; this API addresses every post by its slug, which you can find on any search or list result.",
  "code": "not_found"
}

Over MCP

An MCP tool has no status code, so failures come back as a tool error carrying the same message. The 402 case carries the same code value in its error data, so a client that speaks both surfaces sees one taxonomy.