Skip to content
Operate reliablyErrors

Operate reliably

On this page

Error Codes

When a request fails outright, the API returns a consistent error object with a machine-readable code and human-readable message. If a structured result does not pass the acceptance checks, the JSON API returns HTTP 422,success: false and RESULT_NOT_ACCEPTED. A score of 70 or more does not override failed validity or requested-field checks. Refused requests charge zero customer credits and include an explanation; unfinished values and per-field diagnostics are not returned.

Error Format

All errors follow the same structure:

Error Response
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description of what went wrong"
  }
}

Error Codes

CodeHTTP StatusDescription
RESULT_NOT_ACCEPTED422The requested structured result did not pass acceptance checks. Read metadata.requestedFields.missing, metadata.groundingCoverage and metadata.completeness to see which check failed. Acceptance requires every declared field returned, at least half of all returned values found in the page text, and more than 30% of the fields in the answer populated (completeness above 0.3). An answer at or below that share is refused, so request fewer fields. The quality score must also be at least 70. Review the source and requested fields before starting new work. No unfinished data is returned; customer credits charged are zero.
INVALID_REQUEST400The request body is missing required fields, has the wrong types, or exceeds a size limit (extract over 4,000 characters, schema over 10 KB, html over 5,000,000 bytes). A request body over 4.5 MB never reaches this check: the hosting platform answers it with a plain-text HTTP 413. An unknown field in a scrape or extract request is refused and named. A schema is refused only when it is unusable, and the message names the keyword; unsupported keywords are carried unchecked.
INVALID_URL400The URL points at a private or internal address or at a port other than 80, 443, 8080 or 8443, including when reached through a redirect, or uses a scheme other than http or https. Every redirect hop is checked.
UNAUTHORIZED401Missing, invalid or expired credential. For MCP OAuth, sign in again and reconnect the client. For REST or headless use, check the API key from /dashboard/keys. Only exact recorded requests work without any credential; OAuth does not also need an API key.
FETCH_FAILED422The target URL could not be fetched, at 0 credits. A server error or a timeout may succeed later; a 503 with a usable Retry-After passes that wait back as a header, in seconds. A 404 or 410 from the source, a 2xx page saying it was not found, a response over 10 MB or more than 10 redirects fails the same way every time: check the URL instead of retrying. Reddit URLs also return this code, with a message that Reddit is not supported yet; retrying those will not help. On a plain fetch, a PDF, image, audio, video, font, archive, office file or Windows executable returns this code at 0 credits when its media type or first bytes identify it; with render set to always, a file served with one of those media types is refused the same way, and any other file the browser downloads instead of showing ends as RENDER_REQUIRED at 0 credits (see How Contrie fetches a page). Files are not supported yet, and retrying will not help.
SOURCE_REFUSED422The source refused an automated request — it answered 401, 403, 429 or 451, or answered with an anti-bot challenge page instead of the page. That is a decision, not an outage, so retrying the same URL is unlikely to help. Use the site's official API if it has one. It is also the answer, at 0 credits and before any request for the page, when the site's robots.txt disallows the page for User-agent: Contrie or sets Content-Signal: ai-input=no; the message says so. The exceptions are 429 and a challenge page, which mean “not now”: the message says to retry later, with or without a header. A challenge page never carries one. When the source sends a usable Retry-After, it comes back as a response header in whole seconds: a date is converted to seconds, and the wait is kept between 1 second and one day (86,400). It travels only as a REST header; MCP tool results carry the code and message. The same refusal rules apply when render is always or an automatic render reaches the source: a recognized HTTP refusal or a challenge still present when rendering completes is SOURCE_REFUSED, with a usable wait header only for 429. An explicit browser render lets the page's own scripts run within the original deadline; a challenge they resolve does not itself prevent returning the real page. Browser startup failures, timeouts and other navigation failures retain their rendering error behavior.
RENDER_REQUIRED422The source did not provide enough readable content for this request. Review page access and your request settings. If you disabled rendering, you can explicitly enable it for a new request; the default already permits it. Alternatively, send HTML you are authorized to use to /api/v1/extract.
RATE_LIMITED429More requests in the current sliding one-minute window than the account's plan allows, counted across all of its keys (10 on Free, 30 on Builder, 100 on Pro), or about 20 failed authentication attempts a minute from one client. Check the Retry-After header (seconds) before retrying.
PAGE_LIMIT_REACHED429Credits are unavailable for this request. Either your own requests already in progress are holding them, in which case the response carries a Retry-After and they finish on their own, or the monthly quota is spent (200 credits on the Free plan), in which case there is no Retry-After because it resets on the 1st of the calendar month. On the Free plan the month's Free service capacity can run out before the credits do; the message says so, and it also returns on the 1st.
SERVICE_UNAVAILABLE503A required service is temporarily unavailable. If the result of an earlier request is uncertain, recover it with its original idempotency key and inputs.
MODELS_UNAVAILABLE503Extraction capacity is temporarily unavailable. Retry later; no credits were charged.
EXTRACTION_FAILED500An unexpected error prevented a confirmed extraction response. Recover the original request before deciding to start new work; report persistent failures.
INSUFFICIENT_SCOPE403The OAuth connection lacks the contrie:account scope. Reconnect with a supported client or use an API key.
PAYMENT_REQUIRED402A keyless call to a resource that accepts per-call payment; the response carries the payment requirements. Not enabled today: use an API key.
NOT_FOUND404The monitor or resource id does not exist or does not belong to this account.
IDEMPOTENCY_CONFLICT409This Idempotency-Key was already used with different request inputs. Use a new key for new work.
REQUEST_IN_PROGRESS409A request with this Idempotency-Key is still running. Retry after the Retry-After delay to receive its retained response.
IDEMPOTENCY_EXPIRED409 / 410For extraction, 409 means the retained response for this key has expired (24 hours). The operator-enabled snapshot verification pilot at /api/v1/verification/runs returns 410 when a matching retry identity remains but its saved run has expired: run content is available for 7 days and hashed retry identities expire after 30 days. Neither response starts new work. Use a new key only for deliberately new work.
IDEMPOTENCY_OUTCOME_UNKNOWN409The earlier request expired without a confirmed response and the key starts no more work. GET /api/v1/account/status shows only this month's totals, not whether this request was charged; contact support with the Idempotency-Key to reconcile it, and use a new key only for deliberately new work.
INFERENCE_BUDGET_EXCEEDED429A processing limit stopped the request, at 0 credits. The causes share this code: the request's own time or cost budget, the plan's per-request credit cap (rendering included), a daily inference cap (one per account, one shared by all accounts), or this account's service budget. The daily caps reset at 00:00 UTC. The service budget does not reset daily: the message names it when it is the cause, account status shows the restriction, and the next paid invoice adds budget. A smaller request (fewer fields, a shorter page, or render "never") may succeed. No Retry-After is sent.
INFERENCE_UNAVAILABLE503Spending controls were unavailable, or model work is paused service-wide, so no further model was called and no credits were charged. Retry later.

Handling Errors

  • Always check the success field before accessing data — JSON refusals use HTTP 422. A streaming response may have already started with HTTP 200, so always inspect its final result event too.
  • Use the error.code field for programmatic error handling, not the HTTP status alone
  • Use the error.message field to display user-friendly error messages
  • Back off for the number of seconds in Retry-After whenever it is present, and treat its absence as meaning waiting will not help. It is present on RATE_LIMITED and on refusals that clear without you changing anything. The exceptions tell you in their message to try again, even when the header is absent: a SOURCE_REFUSED whose source rate-limited us or answered with a challenge page, FETCH_FAILED (except for Reddit, binary files, a missing page (404 or 410), a page over 10 MB and a redirect loop), SERVICE_UNAVAILABLE, MODELS_UNAVAILABLE and INFERENCE_UNAVAILABLE. For those, back off and try again later even though no wait was named. The excepted FETCH_FAILED causes fail the same way on every retry, and their message says so: check the URL instead
  • One more exception: INFERENCE_BUDGET_EXCEEDED carries no Retry-After but is not always permanent. Try a smaller request first; if that also fails and the message does not name this account's service budget, a daily cap is the likely cause, and it clears at 00:00 UTC
  • For MODELS_UNAVAILABLE, wait a few minutes before retrying — it is not the same as a per-account rate limit
  • For RENDER_REQUIRED, don't repeatedly submit an unchanged request. Check whether the page is accessible and contains the requested information. If you chose render: "never", consider a new request with automatic rendering, or send authorized HTML to /api/v1/extract instead. None of these options guarantees an accepted result. Recovering the same completed request returns its original refusal; use a new request only after deciding to try changed inputs or conditions.
  • For a lost or uncertain result, use the original idempotency key and unchanged inputs to recover it. Recovery returns the original outcome and does not retry a completed refusal. Start a new request only after deciding to try again; report persistent failures.