Appearance
Error codes
Every error response uses the { error } envelope with a stable machine-readable code. Build your error handling on codes; never parse messages.
json
{
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Not enough credits to start this request.",
"correlation_id": "8f4f1f0e-…"
}
}Request & validation
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
VALIDATION_ERROR | 400 | The request body/query failed validation; details lists the violations. A rejected extractRules or jsScenario names its reason first in the message: EXTRACT_RULES_* (data extraction), JS_SCENARIO_* (page interactions). | Fix the payload. Do not retry unchanged. |
VALIDATION_ERROR | 422 | The request is well-formed but exceeds a limit or rule: too many batch URLs or crawler pages for the plan, an invalid aiExtractRules schema. | Reduce the request or upgrade the plan. |
SSRF_BLOCKED | 403 | The target URL points to a private, local or otherwise forbidden address. Only public http/https targets are allowed (redirects are re-checked too). | Use a public target URL. |
TARGET_HOST_NOT_FOUND | 422 | The target hostname does not exist in DNS (details.dnsErrorCode, e.g. ENOTFOUND) - usually a typo in the URL. Rejected before any work; nothing is charged. | Fix the hostname. Do not retry unchanged. |
TARGET_DNS_UNAVAILABLE | 503 | The target hostname could not be resolved right now: the platform resolver timed out or failed (details.dnsErrorCode, e.g. EAI_AGAIN). Not the target's fault. Rejected before any work; nothing is charged, and a site API request it ends gives back what its earlier steps charged. | Retry after details.retry_after_ms. |
Validation reasons for extractRules and jsScenario
These are not separate error codes: a 400 VALIDATION_ERROR names one of them first in error.message, so a client can tell the cause without parsing the rest.
| Reason | Meaning |
|---|---|
EXTRACT_RULES_INVALID | A rule is malformed; the message names the field. |
EXTRACT_RULES_TOO_DEEP | More than 3 levels of rule maps (the top level plus two nested output maps). |
EXTRACT_RULES_TOO_MANY | More than 100 rules in total, nested rules included. |
JS_SCENARIO_INVALID_STEP | A step has an unknown action or invalid fields. |
JS_SCENARIO_TOO_MANY_STEPS | More than 50 steps. |
JS_SCENARIO_TOO_MANY_EVALUATE_STEPS | More than 10 evaluate steps. |
JS_SCENARIO_DURATION_EXCEEDED | The declared wait durations and step timeoutMs add up to more than the 120-second scenario budget. |
The limits themselves are listed in data extraction and page interactions.
Transport
Raised by the HTTP layer before the request reaches an endpoint.
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
NOT_FOUND | 404 | No endpoint matches this path and method. | Check the path, the method and the /api/v1 prefix. |
PAYLOAD_TOO_LARGE | 413 | The request body exceeds the accepted size. | Split the payload, e.g. into several batch requests. |
UNSUPPORTED_MEDIA_TYPE | 415 | The Content-Type is not one the endpoint accepts. | Send application/json. |
SERVICE_SHUTTING_DOWN | 503 | The API instance that got the request is shutting down during a rolling update and refused it before any work started (the response closes the connection). | Retry after a short pause - another instance takes the request. Safe for POST too. |
SERVICE_UNAVAILABLE | 503 | A platform data store the request needs did not answer in time, for example while it restarts. | Retry after the Retry-After seconds. |
Authentication & permissions
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
INVALID_API_KEY | 401 | Missing, malformed, revoked or unknown API key. | Check the x-api-key header and the key status. |
INVALID_ACCESS_TOKEN | 401 | The dashboard session token is missing or expired. | Re-authenticate in the dashboard. |
PERMISSION_DENIED | 403 | The key/user lacks the scope for this endpoint. | Use a key with the required scope (e.g. scrape:web). |
ORGANIZATION_ACCESS_DENIED | 403 | You are not an active member of the selected workspace, or the workspace is not active; a valid API key of a blocked workspace gets it too. Also the errorCode of an async request, batch item, crawl page or no-code item that did not run because the workspace was blocked (not charged). | Check the x-organization-id header and your membership; for a blocked workspace, contact support. |
Account & sign-in
Returned by the dashboard sign-up, sign-in and account endpoints.
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
EMAIL_ALREADY_REGISTERED | 409 | An account with this email already exists. | Sign in, or reset the password. |
EMAIL_ALIAS_NOT_ALLOWED | 422 | Sign-up does not take a + sub-address (name+tag@domain): it delivers to the same mailbox. | Sign up with the address itself. A team invite may still name an alias. |
EMAIL_DOMAIN_NOT_ALLOWED | 403 | Sign-up is not open for this email domain yet. Existing accounts sign in as usual. | Sign in with an existing account, or ask a team owner for an invite. |
EMAIL_ALREADY_VERIFIED | 409 | The email address is already confirmed. | Nothing to do. |
EMAIL_VERIFICATION_TOKEN_INVALID / EMAIL_VERIFICATION_TOKEN_EXPIRED | 401 | The confirmation link is wrong, already used or expired. | Request a new confirmation email. |
EMAIL_VERIFICATION_REQUIRED | 403 | The action sends email in ScrapeIt's name (a team invite) and needs a confirmed email address. | Confirm your email address first. |
PASSWORD_RESET_TOKEN_INVALID / PASSWORD_RESET_TOKEN_EXPIRED | 401 | The password reset link is wrong, already used or expired. | Request a new reset link. |
EMAIL_DELIVERY_UNAVAILABLE | 503 | The email could not be sent right now. | Retry in a few minutes. |
INVALID_CREDENTIALS | 401 | Wrong email or password. | Check both. 10 wrong passwords in a row lock sign-in (429 RATE_LIMIT_EXCEEDED). |
USER_BLOCKED | 403 | The user account is blocked. | Contact support. |
REFRESH_TOKEN_REVOKED / REFRESH_TOKEN_EXPIRED / REFRESH_TOKEN_REUSED | 401 | The dashboard session has ended: signed out, expired, or its refresh token was used twice, which ends every session of the account. | Sign in again. |
OAUTH_IDENTITY_NOT_FOUND | 404 | No connected sign-in of this provider on the account, or an unknown provider. | Check the provider in Settings → Connected accounts. |
OAUTH_IDENTITY_LAST_CREDENTIAL | 409 | Disconnecting this provider would leave the account without a way to sign in. | Set a password or connect another provider first. |
Social sign-in (Google, GitHub) is a browser redirect, so its failures come back to the dashboard sign-in page as a code, not as an HTTP error:
| Code | Meaning |
|---|---|
OAUTH_PROVIDER_DISABLED | Sign-in with this provider is switched off. |
OAUTH_STATE_INVALID | The sign-in attempt expired or was started in another browser. Start it again. |
OAUTH_ACCESS_DENIED | You cancelled the sign-in at the provider. |
OAUTH_EXCHANGE_FAILED | The provider did not complete the sign-in. Try again. |
OAUTH_EMAIL_MISSING / OAUTH_EMAIL_UNVERIFIED | The provider shared no email, or one it has not verified. Verify it at the provider, or sign up with email and password. |
OAUTH_ACCOUNT_LINK_REQUIRES_SIGN_IN | A password account with this email has not confirmed it. Sign in with the password, confirm the email and connect the provider in Settings → Connected accounts. |
Workspaces, members & invites
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
ORGANIZATION_NOT_FOUND | 404 | Unknown workspace id. | Check the x-organization-id header. |
ORGANIZATION_ROLE_FORBIDDEN | 403 | Your role in the workspace cannot do this: for example a developer revoking another member's API key or granting scopes the role does not have. | Ask an owner or admin. |
ORGANIZATION_MEMBER_NOT_FOUND | 404 | No such member in the workspace. | Reload the member list. |
ORGANIZATION_LAST_OWNER_CONFLICT | 409 | The change would leave the workspace without an owner. | Make another member an owner first. |
ORGANIZATION_ALREADY_MEMBER | 409 | The invited user is already a member. | Nothing to do. |
ORGANIZATION_INVITE_INVALID / ORGANIZATION_INVITE_EXPIRED | 401 | The invite link is wrong or expired. | Ask for a new invite. |
ORGANIZATION_INVITE_ALREADY_ACCEPTED | 409 | The invite has been accepted. | Open the workspace. |
ORGANIZATION_INVITE_EMAIL_MISMATCH | 403 | The invite was sent to another email address than the signed-in account's. | Sign in with the invited address. |
ORGANIZATION_INVITE_NOT_FOUND | 404 | Unknown invite id. | Reload the invite list. |
ORGANIZATION_INVITE_ALREADY_PENDING | 409 | This email already has a pending invite. | Revoke it to send a new one. |
ORGANIZATION_LIMIT_REACHED | 409 | The account cannot create another workspace. | Use the existing workspace. |
Limits & billing
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
INSUFFICIENT_CREDITS | 403 | The workspace balance cannot cover the estimated cost. Credits reserved by your in-flight requests count as spent (details.reserved_credits). Rejected before any work starts. | Top up / upgrade the plan. |
PLAN_FEATURE_NOT_AVAILABLE | 403 | The workspace plan does not include this product (details.feature: batch_scraping, crawler or ai_extraction). Rejected before any work starts. | Upgrade the plan. |
CONCURRENCY_LIMIT_EXCEEDED | 429 | All concurrent request slots of your plan are busy. Rejected before queue and browser startup. | Retry after an active request finishes, add client-side throttling, or upgrade. See Concurrency & limits. |
BILLING_SUBSCRIPTION_ALREADY_ACTIVE | 409 | A Checkout was requested while the workspace already has a paid subscription (active, trialing or past due). | Change the plan in the Customer Portal (dashboard Billing → Manage billing) instead of starting a new Checkout. |
SUBSCRIPTION_NOT_FOUND | 402 | The workspace has no active plan. | Choose a plan on the dashboard Pricing page. |
PLAN_NOT_FOUND | 404 | Unknown plan code. | Pick a plan listed on the Pricing page. |
BILLING_PRICE_NOT_FOUND | 404 | The plan has no price for the chosen billing interval. | Choose the other interval or another plan. |
BILLING_PRICE_NOT_PUBLISHED | 409 | The plan's new price is not open for Checkout yet. | Retry later or contact support. |
BILLING_CUSTOMER_NOT_FOUND | 404 | The Customer Portal opens once the workspace has paid through Checkout. | Buy a plan first. |
BILLING_INVOICE_NOT_FOUND / BILLING_CHECKOUT_SESSION_NOT_FOUND | 404 | Unknown invoice or Checkout session of the workspace. | Reload the billing page. |
STRIPE_CUSTOMER_CREATE_FAILED / STRIPE_CHECKOUT_SESSION_CREATE_FAILED / STRIPE_PORTAL_SESSION_CREATE_FAILED / STRIPE_SUBSCRIPTION_SYNC_FAILED | 502 | Stripe did not complete the call. Nothing was charged by this request. | Retry; if it persists, contact support. |
RATE_LIMIT_EXCEEDED | 429 | Too many requests in a short window: the request rate limit of every /api/v1 endpoint (per API key, user or IP; sent with a Retry-After header), sign-in and other account endpoints, the support form, and - where a per-target-domain rate applies to your workspace - Web Scraping API requests to one domain (details.retry_after_ms). | Back off and retry with jitter, after Retry-After / retry_after_ms when present. |
Scraping execution
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
SCRAPE_TIMEOUT | 504 | The end-to-end timeoutMs budget ran out before a result was ready. | Retry; raise timeoutMs; reduce work (e.g. blockResources: true). |
| - | 201* | Web Scraping API: the target kept serving an anti-bot challenge. Reported in the body via non_billable_failure_category: "anti_bot_challenge", not as an HTTP error. | Try jsRendering: true, proxyType: "residential", a different proxyCountry. |
TARGET_BLOCKED | 502 | Product APIs (Allegro, Amazon, Apartments.com, Booking.com, Google Maps, Google SERP, Walmart, Yelp, Redfin, Airbnb, Tripadvisor, Yellow Pages, eBay, Glassdoor, Zillow): the site gave no usable data - a block or challenge page, a response without result data, or an Allegro search rejected with GraphQL errors (listed in details.graphql_errors). | Retry later. For graphql_errors, check page_token first - a stale or edited cursor is the usual cause. |
TARGET_RESPONSE_TOO_LARGE | 502 | Product APIs: answers from this site were cut at the response size limit three times within an hour, so your workspace's requests to it are paused until an hour after the last cut answer (limits). The request ends at once and costs nothing. The Web Scraping API reports the same pause in the body via non_billable_failure_category: "response_too_large". | Retry after an hour; check that the input names a page and not a file. |
NOT_FOUND | 404 | Product APIs - Allegro offer API: allegro.pl answered the offer with HTTP 404 or 410 (details.target_http_status) - the offer was removed or the id/URL is wrong. Google Maps place API: Google answered and has no place under the id. Walmart product and Yelp business APIs: the site answered HTTP 404 for the item id or alias. Amazon product and Best Sellers APIs: no page for the ASIN or category. Redfin: no property under the id, or no place redfin.com suggests for the search location. Airbnb listing API: no listing under the id. Tripadvisor place and Yellow Pages business APIs: the site answered HTTP 404 (or 410) for the URL. eBay item API: eBay answered HTTP 404 for the item number. Glassdoor: no place Glassdoor suggests for the location, no job Glassdoor shows under the job_id, or no company under the company_id. Booking.com: the search destination was not recognized, there is no page at the URL, or the hotel_id is unknown. | Check the offer id or URL; do not retry unchanged. |
PROXY_PROVIDER_NOT_FOUND | 422 | No active proxy serves the requested proxyType / proxyCountry (datacenter when proxyType is not set) - for a sessionId, none is configured that can hold one exit across requests; for the Google APIs, no exit in the country of gl. Rejected before any work. | Change proxyType or proxyCountry, or drop sessionId; for a Google API, choose another gl. |
PROXY_UNAVAILABLE | 503 | The proxies that serve the request (its proxyType / proxyCountry or sessionId) exist but are all briefly out: cooling down after failures or at their traffic limit. A request is never sent without a proxy instead. details.retry_after_ms is when the first cooldown ends (absent when only traffic limits keep them out). Rejected before any work. | Retry later with the same parameters, after details.retry_after_ms when present. |
BROWSER_CAPACITY_EXHAUSTED | 503 | A rendered request (jsRendering: true, a screenshot, a product API step) found every browser busy and the queue for starting a new one already too long to clear within its wait. Refused within seconds instead of running into SCRAPE_TIMEOUT; nothing is charged. details.retry_after_ms and Retry-After say when to come back. | Retry after Retry-After (the SDKs do it automatically); lower your parallelism if it keeps happening. |
SCRAPE_SESSION_LIMIT_EXCEEDED | 429 | A new sessionId would exceed the 100 active sticky sessions of the workspace (details.max_active_sessions). A session expires 10 minutes after its last successful request (details.session_ttl_seconds). Rejected before any work. | Reuse an existing sessionId, or wait for one to expire. |
INTERNAL_ERROR | 500 | Unexpected platform failure. | Retry; if it persists, contact support with the correlation_id. |
* A completed scrape returns HTTP 201 with the outcome inside the body - see the response schema.
Resources
| Code | HTTP | Meaning |
|---|---|---|
JOB_NOT_FOUND | 404 | No job with this id in your workspace. |
BATCH_JOB_NOT_FOUND / BATCH_JOB_ITEM_NOT_FOUND | 404 | Unknown batch job / item id. |
SCRAPER_JOB_NOT_FOUND | 404 | Unknown crawler job id. |
SCRAPE_REQUEST_NOT_FOUND | 404 | Unknown scrape request id. |
SCRAPE_REQUEST_CANNOT_BE_STOPPED | 409 | A single Web Scraping API request cannot be stopped on its own. A request that belongs to a batch is stopped by stopping its batch job. |
FILE_NOT_FOUND | 404 | Unknown file id (or it belongs to another workspace). |
FILE_DOWNLOAD_TOKEN_INVALID / FILE_DOWNLOAD_TOKEN_EXPIRED | 401 | The short-lived download token is wrong or expired - request a fresh one. |
WEBHOOK_ENDPOINT_NOT_FOUND / WEBHOOK_DELIVERY_NOT_FOUND | 404 | Unknown webhook endpoint / delivery id. |
WEBHOOK_SECRET_NOT_CONFIGURED | 422 | An async request sent webhookUrl before the request webhook signing secret was created. Create it on the dashboard Webhooks page or with POST /api/v1/webhooks/request-signing-secret, then resend. Nothing was queued. |
API_PRODUCT_NOT_FOUND | 404 | Unknown API product code. |
API_PRODUCT_NOT_EXECUTABLE | 422 | The product is switched off for maintenance; nothing was charged. Retry later. |
SCRAPER_PRODUCT_NOT_FOUND / SCRAPER_PRODUCT_NOT_EXECUTABLE | 404 / 422 | Unknown no-code scraper, or one switched off for maintenance; nothing was charged. |
API_KEY_NOT_FOUND | 404 | Unknown API key id in the workspace. |
API_KEY_REVOKED | 409 | A revoked key cannot be rotated: create a new key instead. |
OBJECT_STORAGE_UNAVAILABLE | 500 | File storage did not answer the download. Retry. |
ALERT_EVENT_NOT_FOUND | 404 | Unknown alert id in the workspace. |
AI_CHAT_DISABLED | 403 | The AI assistant is switched off in this environment. |
AI_CHAT_TOKEN_BUDGET_EXCEEDED | 429 | The workspace used up its daily AI assistant budget. Try again tomorrow. |
AI_CHAT_DAILY_REQUEST_LIMIT_EXCEEDED | 429 | The workspace sent its daily number of AI assistant messages. details.limit is the limit and details.resets_at when it starts again (00:00 UTC). |
AI_CHAT_PROVIDER_ERROR | 502 / 504 | The AI model provider failed or did not answer in time. Retry. |
AI_AGENT_CONVERSATION_NOT_FOUND | 404 | Unknown AI assistant conversation, or one of another user. |
AI_EXTRACTION_DISABLED | 403 | aiExtractRules was sent but AI extraction is not configured in this environment. |
AI_EXTRACTION_DISABLED | 422 | aiExtractRules was sent but AI extraction is switched off for this product, or the product (Website Crawler) does not run it yet. |
Codes of the platform administration, of Stripe's calls to ScrapeIt and of internal steps a request recovers from are not listed: a workspace does not receive them.
Retry guidance
- Safe to retry with backoff:
SCRAPE_TIMEOUT,INTERNAL_ERROR,RATE_LIMIT_EXCEEDED,CONCURRENCY_LIMIT_EXCEEDED(after a slot frees up),SERVICE_SHUTTING_DOWN(after a short pause),SERVICE_UNAVAILABLE(afterRetry-After),TARGET_BLOCKED(later),TARGET_RESPONSE_TOO_LARGE(after the pause, up to an hour),PROXY_UNAVAILABLE(afterdetails.retry_after_ms),BROWSER_CAPACITY_EXHAUSTED(afterRetry-After),TARGET_DNS_UNAVAILABLE(afterdetails.retry_after_ms). - Fix before retrying:
VALIDATION_ERROR,SSRF_BLOCKED,TARGET_HOST_NOT_FOUND,INVALID_API_KEY,PERMISSION_DENIED,INSUFFICIENT_CREDITS,PLAN_FEATURE_NOT_AVAILABLE,NOT_FOUND(an offer, place, product, business or property that does not exist),PROXY_PROVIDER_NOT_FOUND,SCRAPE_SESSION_LIMIT_EXCEEDED(reuse a session or wait for one to expire). - Proxy/network failures inside a scrape are retried automatically by the platform across healthy proxy providers before you ever see an error - see Web Scraping API.
The Retry-After header
When a response carries Retry-After, wait that many seconds before the next request - it is the earliest moment the retry can succeed. Which responses send it:
| Response | Retry-After |
|---|---|
429 RATE_LIMIT_EXCEEDED (request rate) | Always: the seconds until the request rate limit window resets; details.retry_after_ms carries the same in milliseconds. The header is readable from browsers (CORS-exposed). |
429 RATE_LIMIT_EXCEEDED (target domain) | When details.retry_after_ms is present: the same wait in whole seconds, rounded up. |
429 RATE_LIMIT_EXCEEDED (sign-in lockout) | Always: the seconds until sign-in to the account opens again. 10 wrong passwords in a row lock it for 15 minutes, each next lock in a row doubles up to 24 hours, a successful sign-in resets the count and a password reset lifts the lock; details.retry_after_ms carries the same in milliseconds. |
429 CONCURRENCY_LIMIT_EXCEEDED | No header: a slot frees up as soon as one of your active requests finishes - retry with exponential backoff and jitter (see Concurrency & limits). |
503 PROXY_UNAVAILABLE | When details.retry_after_ms is present: the same wait in whole seconds, rounded up. Without it, back off (minutes - the proxies are at their traffic limit). |
503 BROWSER_CAPACITY_EXHAUSTED | Always: a few seconds, about one browser start; details.retry_after_ms carries the same in milliseconds. |
503 SERVICE_SHUTTING_DOWN | No header: retry after a short pause (around a second), on a new connection. If a proxy or load balancer in front of the API adds Retry-After to a 503, honour it. |
503 SERVICE_UNAVAILABLE | Always: a few seconds, about one data store restart; details.retry_after_ms carries the same in milliseconds. |
Without the header, back off exponentially with jitter instead of retrying in a tight loop.