Errors
Every error has the same shape and a stable code. Branch on the code; show the message.
Every error has the same shape and a stable code. Branch on the code; show the message.
Error shape
{
"error": {
"code": "INVALID_PARAM",
"message": "Unsupported country_code. Use a lowercase ISO 3166-1 alpha-2 code.",
"param": "country_code",
"docs": "/docs/errors#invalid_param"
}
}codeis stable and machine-readable: branch on it.messageis for humans and may be reworded.paramnames the offending parameter when there is one.docslinks to the code's entry below.Every response, error or not, carries
X-Request-Id.
Codes
Code | HTTP | Meaning |
|---|---|---|
400 | Missing parameter | |
400 | Invalid parameter | |
401 | Unauthorized | |
403 | Demo workspace | |
402 | Insufficient credits | |
404 | Not found | |
409 | Conflict | |
429 | Concurrency limit reached | |
429 | Rate limited | |
502 | Target failed | |
503 | Temporarily unavailable | |
501 | Not implemented | |
500 | Internal error |
MISSING_PARAM {#missing_param}
HTTP 400 · Missing parameter. A required parameter was not sent. param names it.
What to do: Add the parameter. For GET requests, remember to URL-encode the target
url.
INVALID_PARAM {#invalid_param}
HTTP 400 · Invalid parameter. A parameter was sent but its value is not accepted: wrong type, out of range, an unsupported country, a private target URL, an entry in asins that is not an ASIN (param is then asins[i]), or two options that cannot be combined. param names it.
What to do: Read
message: it says exactly what is expected.
UNAUTHORIZED
HTTP 401 · Unauthorized. No API key was sent, or the key is unknown or revoked.
What to do: Send your key as
Authorization: Bearer <key>,X-API-Key: <key>or?api_key=<key>. Keys are in the dashboard under API keys.
DEMO_READ_ONLY {#demo_read_only}
HTTP 403 · Demo workspace. The key belongs to the public, read-only demo workspace. It can be seen in the dashboard but cannot make requests or spend credits.
What to do: Create an account (5,000 free credits) and use your own API key.
INSUFFICIENT_CREDITS {#insufficient_credits}
HTTP 402 · Insufficient credits. Your credit balance cannot cover this request. Nothing was charged. Batches are all-or-nothing: the whole batch must be affordable.
What to do: Buy a credit pack (credits never expire), or enable auto top-up in Billing.
NOT_FOUND {#not_found}
HTTP 404 · Not found. The job, batch or structured endpoint does not exist, or belongs to another account.
What to do: Check the id. Job results expire after 72 hours; the job itself remains.
CONFLICT
HTTP 409 · Conflict. The request conflicts with the resource's current state: a request with the same Idempotency-Key is still in progress, or a job that is already running was asked to cancel.
What to do: For idempotency conflicts, retry after a few seconds. Running jobs finish on their own (and are free if they fail).
CONCURRENCY_LIMIT {#concurrency_limit}
HTTP 429 · Concurrency limit reached. All of your concurrent request slots are busy. Sync requests are refused immediately; async jobs simply wait in the queue.
What to do: Retry with backoff, or use async jobs, which queue instead of failing. Concurrency grows with lifetime credits purchased.
RATE_LIMITED {#rate_limited}
HTTP 429 · Rate limited. Too many API calls with this key in a short window. Separate from concurrency: this counts calls, including cheap ones such as job polling.
What to do: Wait
Retry-Afterseconds. Prefer webhooks to polling job status in a tight loop.
TARGET_FAILED {#target_failed}
HTTP 502 · Target failed. We could not get a usable answer from the target site after our retries: it blocked us, rate-limited us, errored, or never responded. You were not charged.
What to do: Retry; consider
premium=trueorultra_premium=truefor heavily protected sites, orrender=truefor JavaScript-built pages.
UNAVAILABLE
HTTP 503 · Temporarily unavailable. A dependency on our side is down or overloaded. Nothing was charged.
What to do: Retry with exponential backoff. Status updates at /status.
NOT_IMPLEMENTED {#not_implemented}
HTTP 501 · Not implemented. The endpoint or option exists in the API surface but is not available yet. Structured data endpoints answer this with "message": "Coming soon" and "coming_soon": true; nothing is charged.
What to do: For Amazon product data use the Amazon Product API,
POST /v1/products. Contact support if you need another endpoint sooner.
INTERNAL
HTTP 500 · Internal error. Something failed on our side. Nothing was charged, and the failure was logged with your X-Request-Id.
What to do: Retry. If it persists, contact support with the
X-Request-Idheader value.
Retrying
Safe to retry with exponential backoff: CONCURRENCY_LIMIT, RATE_LIMITED (honour Retry-After), TARGET_FAILED, UNAVAILABLE, INTERNAL. Do not retry 400/401/402/404 without changing something. For creates, send an Idempotency-Key so a retry can never double-charge.
import time, requests
RETRYABLE = {"CONCURRENCY_LIMIT", "RATE_LIMITED", "TARGET_FAILED", "UNAVAILABLE", "INTERNAL"}
def scrape(url, attempts=4):
for i in range(attempts):
r = requests.get("https://api.scrapely.dev/v1/scrape", params={"url": url},
headers={"Authorization": "Bearer YOUR_API_KEY"}, timeout=70)
if r.ok:
return r.json()
code = r.json()["error"]["code"]
if code not in RETRYABLE or i == attempts - 1:
raise RuntimeError(code)
time.sleep(float(r.headers.get("Retry-After", 2 ** i)))