Incantory
Sign in

Rate limits and errors

Rate limit headers, 429 handling, the stable error codes the API returns, and how the SDKs and CLI retry.

Rate limits

The API limits requests per token and, for anonymous calls, per IP. Responses subject to a limit carry:

HeaderMeaning
X-RateLimit-LimitRequests allowed in the current window
X-RateLimit-RemainingRequests left in the window
X-RateLimit-ResetWhen the window resets, as a Unix timestamp in seconds
Retry-AfterOn a 429: seconds to wait before retrying

A request over the limit answers 429 with code rate_limited. Wait for Retry-After and try again; do not retry in a tight loop. Authenticated requests get higher limits than anonymous ones, so send a token from servers and CI.

Some actions have their own per-user limits on top (commenting, for example), which answer the same 429.

Error format

json
{ "error": { "code": "not_found", "message": "Not found", "requestId": "..." } }

details is present when there is more to say, for example the list of validation issues. Branch on code, never on message.

Error codes

Request errors

StatusCodeMeaning
400validation_failedThe body, query or path failed validation; details lists each issue with its path
400invalid_jsonThe body is not valid JSON
400invalid_cursorThe pagination cursor is malformed
413payload_too_largeThe body is over the endpoint's limit (1 MB by default)
415json_requiredA cookie-authenticated write was not sent as JSON
422invalid_package, unknown_tags, license_not_allowed, …The request is well-formed but not acceptable
429rate_limitedToo many requests; see Retry-After
500internalA bug on our side; the response carries only the requestId

Permission and state errors

These come from the permission policy and are the same across the API, the website and the MCP server.

StatusCodeMeaning
401unauthenticatedSign in or send a token
403token_scopeThe token lacks the scope this action needs
403token_forbiddenNo token may do this (for example minting tokens)
403not_ownerOnly the owner (or an admin) may do this
403role_requiredNeeds a moderator or admin
403account_suspended, account_bannedThe account cannot make changes
403blockedThe owner has blocked you
403comments_lockedThe thread is locked
403edit_window_closedComments can be edited for 24 hours
403eval_requiredThe label requires a passing eval run on the target version
403version_yankedA label cannot point at a yanked version
403immutable, in_use, invalid_stateThe item cannot change in this way right now
404not_foundMissing, or something you are not allowed to see (private and draft items answer 404, not 403)
410deletedThe item was deleted (a tombstone)

Registry conflicts

StatusCodeMeaning
409slug_takenYou already have a prompt with that slug
409no_changesThe commit is identical to the latest version
409stale_baseSomeone committed since the baseVersion you edited; details.latest has the current number
409version_labelledMove the labels off a version before yanking it
409last_versionThe only live version cannot be yanked; delete the prompt instead
409no_draftThere is no draft to commit

Retries in the SDKs and CLI

The TypeScript and Python SDKs (and the CLI, which uses the TypeScript SDK) retry for you:

  • 429 is retried for every method.
  • 5xx, network errors and timeouts are retried only for idempotent methods (GET, HEAD, PUT, DELETE), never for POST, so a commit is never sent twice.
  • Backoff is exponential with full jitter: 500 ms times 2 to the attempt, capped at 30 seconds. A Retry-After header (seconds or an HTTP date) overrides it, capped at 60 seconds.
  • The default is 3 retries (maxRetries / max_retries), with a 30 second request timeout.

Every failure surfaces as an IncantoryError with status, code, message, requestId, details and retryAfter (retry_after in Python). The CLI maps them to exit codes.