Instant Snap API
Errors
Every failure answers with the same JSON envelope, whatever response_type you asked for. The code is the part to branch on; the message is for a human reading a log.
The error envelope
{
"success": false,
"error_code": "validation_failed",
"error_message": "\"page_timeout_ms\" must be between 1000 and 60000",
"retry_after_seconds": null
}| Field | Type | Default | Description |
|---|---|---|---|
success | boolean | — | Always false on an error. |
error_code | string | — | A stable token. Branch on this, never on the message or on the HTTP status alone. |
error_message | string | — | Human-readable, and it names the offending field where there is one. Its wording is not part of the contract. |
retry_after_seconds | integer | — | Present on refusals that are worth retrying. Mirrored in the Retry-After header. |
Even a by_format request errors in JSON
application/json rather than the format you asked for. Check the status code before you write the body to disk.Every code
| Field | Type | Default | Description |
|---|---|---|---|
validation_failed | 400 | Do not retry | The request body is wrong: an unknown token, a value outside its range, or a combination that cannot be rendered. The message names the field. |
validation_failed (page_source) | 400 | Do not retry | page_source was sent with response_type: by_format. That mode answers with the raw file as the response body, and one body cannot carry both the capture and its source. Use json or cloud_storage. |
validation_failed (document_pdf) | 400 | Do not retry | format: document_pdf was sent with scroll_mode: scroll. That format captures the visible page only. Use scroll_mode: viewport, or format: image_pdf for a scrolling capture. |
quota_exhausted (combo) | 402 | Do not retry until the allowance resets | A capture with page_source costs two units, and the check happens before anything is rendered - so a request refused here has captured nothing and charged nothing. |
s3_destination_invalid | 400 | Do not retry | The s3_delivery block is missing something, or the endpoint is not an https address we are allowed to call. Distinct from validation_failed so you know which half is wrong. |
api_key_invalid | 401 | Do not retry | Missing, malformed, deleted or unknown key — all four answer identically, so a caller cannot use this endpoint to discover which keys exist. |
quota_exhausted | 402 | Do not retry | Your plan allowance for the period is spent. Waiting does not help; a larger plan or the next period does. |
no_trigger_allowance | 403 | Do not retry | This key or subscription is not permitted to start a capture right now. |
feature_not_available | 403 | Do not retry | You sent a field your plan does not include — proxy, headers, cookies or callback_url. Not a 402: no amount of usage turns it into a yes, only a different plan does. |
capture_not_found | 404 | Do not retry | No stored capture with that id belongs to this account. Also what a by_format capture answers, because those are never stored. |
payload_too_large | 413 | Do not retry | The capture is larger than the 64 MiB an inline by_format response can carry: ask for json or cloud_storage instead. For a page-content capture, the page's text is larger than 2 MB; it is not charged, and no response type makes it fit. |
rate_limited | 429 | Retry | Too many requests in the current minute, for your plan or from this address. Wait retry_after_seconds and repeat the identical request. |
queue_full | 429 | Retry later | Your workspace already has 100 async captures waiting or running. Nothing was queued or charged. Let some finish - retry_after_seconds is a hint - rather than retrying in a tight loop. |
internal_error | 500 | Retry once | Something went wrong on our side — for example the page was captured but could not be stored. Not charged. Safe to retry with the same Idempotency-Key. |
capture_failed | 502 | Retry once | The page was accepted but could not be rendered. A 502 rather than a 400 because the fault is upstream of you — your request was fine, the page was not. Not charged, except when the website refused the capture after your free refusals (5% of your plan's monthly captures) are used. |
service_unavailable | 503 | Retry | A service behind the API could not be reached. Carries retry_after_seconds; wait that long and repeat the request. |
capture_busy | 503 | Retry | The capture could not start in time because other captures were still rendering. Not your fault, does not count against quota, and usually succeeds on the next attempt. |
capture_expired | 503 | Retry | An async capture waited 15 minutes without a free browser and was closed. It is the capture's error_code (the capture reads failed), not an answer to your request. Not charged; submit it again. |
idempotency_key_in_use | 409 | Retry | A request with the same Idempotency-Key is still running. Wait retry_after_seconds and repeat it; you will get that request's answer. |
idempotency_key_reused | 409 | Never retry | The Idempotency-Key was already used with a different body, or for a by_format capture, whose file is not kept to replay. Use a new key. |
capture_timeout | 504 | Retry with care | The page did not settle within page_timeout_ms, or the capture could not be stored and answered within the 2 minutes a synchronous request has. Not charged either way. Raising the timeout helps a slow page; it does not help one that never finishes loading. A page that needs longer than a synchronous request allows can be captured async. |
What to retry
| Value | Meaning |
|---|---|
Retry: 429 rate_limited, 503, idempotency_key_in_use | Rate limiting, an unreachable service, a full queue and a request still running all clear on their own. Honour retry_after_seconds, cap your attempts, add jitter, and send the same Idempotency-Key so a retry never captures twice. |
Retry once: 500, capture_failed | A transient fault is worth one more attempt. A second identical failure is a real one — log it and move on. |
Retry later: 429 queue_full | Your async queue is full. A quick retry meets the same queue; wait for captures to finish. The client libraries raise it at once instead of retrying. |
Never retry: 400, 401, 402, 403, 404, 413 | The identical request will fail identically. Something has to change first — the body, the key, the plan or the response type. |
A 504 is a judgement call
page_timeout_ms, or reduce what the capture has to do — scroll_mode: viewport instead of scroll, a lower device_scale_factor — before you try the same request a second time.validation_failed
The commonest error, and the most self-explanatory: the message names the field and the bound or the allowed set.
// "page_timeout_ms" must be between 1000 and 60000
{ "url": "https://example.com", "page_timeout_ms": 90000 }
// "scroll_mode" must be one of "viewport" or "scroll"
{ "url": "https://example.com", "scroll_mode": "full_page" }
// "format" is not valid for media_category "image"
{ "url": "https://example.com", "format": "mp4" }- Omitting a field never causes this. Only a value you sent can be out of range — an omitted one is filled from the defaults.
- A misspelled field name does not cause it either. Unknown fields are ignored, which is why a setting that appears to do nothing is usually a typo.
formatmust agree withmedia_category. Asking for an mp4 as an image is refused rather than silently reinterpreted.
Failures that are not yours
capture_failed and capture_timeout are deliberately 502 and 504 rather than 400: the request was accepted and understood, and it was the page that did not cooperate. Before retrying, check:
- Is the page reachable from the public internet? A staging URL behind a VPN cannot be captured.
- Does it need authentication? Supply it with
cookiesorheaders— but note that both are plan-gated. - Does it block automated browsers? A challenge page renders successfully as a challenge page, so check
page_http_statuson captures that look wrong rather than empty. - Is it simply slow? Raise
page_timeout_mstowards its 60 000 ceiling.
Codes this page does not list
A token this release of the API does not recognise is reported as 502 rather than 500 — a newer renderer must never be able to turn an unexpected code into an internal server error. So treat an unfamiliar error_code the way you would treat a 502: log it, retry once, and do not branch on it.
Did this page answer your question?
Tell us what is missing