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

400 Bad Request
{
  "success": false,
  "error_code": "validation_failed",
  "error_message": "\"page_timeout_ms\" must be between 1000 and 60000",
  "retry_after_seconds": null
}
FieldTypeDefaultDescription
successboolean—Always false on an error.
error_codestring—A stable token. Branch on this, never on the message or on the HTTP status alone.
error_messagestring—Human-readable, and it names the offending field where there is one. Its wording is not part of the contract.
retry_after_secondsinteger—Present on refusals that are worth retrying. Mirrored in the Retry-After header.

Even a by_format request errors in JSON

There is no file to return when a request fails, so the response body is the envelope above and the content type is application/json rather than the format you asked for. Check the status code before you write the body to disk.

Every code

Error code, the status it arrives with, and what to do.
FieldTypeDefaultDescription
validation_failed400Do not retryThe 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)400Do not retrypage_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)400Do not retryformat: 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)402Do not retry until the allowance resetsA 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_invalid400Do not retryThe 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_invalid401Do not retryMissing, malformed, deleted or unknown key — all four answer identically, so a caller cannot use this endpoint to discover which keys exist.
quota_exhausted402Do not retryYour plan allowance for the period is spent. Waiting does not help; a larger plan or the next period does.
no_trigger_allowance403Do not retryThis key or subscription is not permitted to start a capture right now.
feature_not_available403Do not retryYou 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_found404Do not retryNo stored capture with that id belongs to this account. Also what a by_format capture answers, because those are never stored.
payload_too_large413Do not retryThe 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_limited429RetryToo many requests in the current minute, for your plan or from this address. Wait retry_after_seconds and repeat the identical request.
queue_full429Retry laterYour 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_error500Retry onceSomething 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_failed502Retry onceThe 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_unavailable503RetryA service behind the API could not be reached. Carries retry_after_seconds; wait that long and repeat the request.
capture_busy503RetryThe 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_expired503RetryAn 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_use409RetryA 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_reused409Never retryThe 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_timeout504Retry with careThe 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

ValueMeaning
Retry: 429 rate_limited, 503, idempotency_key_in_useRate 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_failedA transient fault is worth one more attempt. A second identical failure is a real one — log it and move on.
Retry later: 429 queue_fullYour 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, 413The 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

Retrying a timeout unchanged usually times out again. Raise 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.

Three requests and the messages they get
// "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.
  • format must agree with media_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:

  1. Is the page reachable from the public internet? A staging URL behind a VPN cannot be captured.
  2. Does it need authentication? Supply it with cookies or headers — but note that both are plan-gated.
  3. Does it block automated browsers? A challenge page renders successfully as a challenge page, so check page_http_status on captures that look wrong rather than empty.
  4. Is it simply slow? Raise page_timeout_ms towards 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