Errors and retries
Every error code, what it means, and which ones to retry.
Every error carries a stable error.code you can branch on, and an error.message written for people. Codes never change; messages may be reworded, so never match on the text.
A call that fails costs nothing.
The shape of an error
{ "error": { "code": "invalid_input", "message": "query is required", "fields": {"input": ["query is required"]} }, "errors": {"input": ["query is required"]}, "request_id": "GN8buUl9sP7R2_0AAH9h"}
error.codeis what to branch on.error.fieldsappears oninvalid_inputand names each field that is wrong.request_ididentifies the request. Send it to support if you need help.
An error happens at one of two moments: when Upscrape receives your request, before any job exists, or while the job runs. The two tables below cover each.
When the request is refused
These come back at once, with no job created and nothing charged.
| HTTP | Code | What it means | What to do |
|---|---|---|---|
| 401 | unauthorized | The API key is missing, wrong or revoked. | Send Authorization: Bearer with a valid key. |
| 402 | quota_exhausted | Your balance cannot cover this call. | Buy credits, then call again. |
| 402 | account_inactive | The account is not active. | Buy credits to activate it, or contact support. |
| 403 | forbidden | This key may not do this, for example a read-only key that tries to run an endpoint. | Use a key with the right access. |
| 403 | contact_for_access | The platform is available by request. | Contact us to enable it. |
| 404 | not_found | The job or monitor does not exist for this account. | Check the ID. |
| 405 | method_not_allowed | The address takes another method; the Allow header names it. | Use POST for /execute. |
| 409 | idempotency_conflict | The Idempotency-Key was already used for a different request. | Use a new key for a new request. |
| 422 | invalid_input | The body does not fit the endpoint's input; error.fields says where. | Fix the input. |
| 422 | credit_limit_exceeded | The price is above your max_credits. | Raise max_credits, or change the input. |
| 422 | api_key_limit_reached | The account already has 25 active keys. | Revoke one first. |
| 422 | monitor_limit_reached | The account has as many active monitors as it can. | Pause or delete one first. |
| 429 | rate_limited | Too many requests in a minute. | Wait Retry-After, then try again. |
| 451 | subject_unavailable | The profile's owner asked not to be returned through Upscrape. | Nothing; this input will not work. |
| 500 | internal_error | Something went wrong on our side. | Retry with the same Idempotency-Key. |
| 503 | temporarily_unavailable | The endpoint cannot run right now. | Try again later, not in a tight loop. |
When the job fails
A job that fails ends with state: "failed", success: false and the error. You see it when you fetch the job, or at once if you sent Prefer: wait and the job failed in that time; the HTTP status then follows the kind of failure, but the code is what to branch on.
{ "job_id": "d7ef67f5-8cb8-4989-8b55-8ba67a1aab4f", "state": "failed", "success": false, "error": { "code": "not_found", "message": "The requested resource was not found." }}
| Code | What it means | Try again? |
|---|---|---|
invalid_input | The platform did not accept the input, for example an ID in the wrong form. | No. Fix the input. |
not_found | What you asked for does not exist, or no longer does. | No. |
website_not_found | The site's host name does not exist. | No. Check the URL. |
forbidden | The data is not public. error.reason is account_private for a private account. | No. |
login_required | The platform shows this data only to signed-in visitors. | No. |
unsupported_content | The URL is not a page or a document Upscrape can read. | No. |
response_too_large | The page is larger than max_body_bytes allows. | Raise max_body_bytes, or set allow_truncated. |
incomplete_result | The page did not hold what the call needs; for an extraction, the message names the fields it could not find. | Not unchanged. Check the input or the fields you asked for. |
blocked | The platform is refusing requests for a while. | Yes, after a pause. |
upstream_rate_limited | The platform is limiting requests for a while. | Yes, after a pause. |
timeout | The call took longer than the endpoint's run time. | Yes. |
upstream_timeout | The platform did not answer in time. | Yes, after a pause. |
upstream_error | The platform's answer could not be read this time. | Yes, after a pause. |
schema_changed | The platform changed the format of what it returns. | Later. We fix the endpoint; tell us if it lasts. |
temporarily_unavailable | The endpoint could not run right now. | Yes, later. |
execution_lost | The job ended without a result. Nothing was stored or charged. | Yes. Send a new request. |
result_unavailable | The job finished but its result cannot be returned. | No. Contact support with the job_id. |
cancelled | The job was cancelled. | Send a new request if you still need it. |
internal_error | Something went wrong on our side. | Yes. |
Retry safely
Retry only the codes marked yes above, and
rate_limitedafter itsRetry-After.Wait longer between attempts each time, for example 1, 2, 4, 8 seconds, and give up at a deadline you choose.
Retry a request with its original
Idempotency-Key, so a retry after a lost response never runs or charges twice.To wait for a slow job, fetch it with
GET /jobs/{id}; don't send the request again.Never retry a key, balance, input or idempotency error unchanged: it fails the same way.
Validation messages
invalid_input says exactly what is wrong: the missing field, the accepted alternatives, the expected type, and an example when the endpoint gives one. REST, MCP and the console show the same messages.
The response also carries the field map at the top level as errors. That is the original shape of this response, kept so older integrations keep working; read error.fields in new code.
Something missing or unclear? Ask the team that builds the endpoints.