Errors & Status Codes
When a request fails, success is false and an error field describes what happened. The HTTP status code tells you whether the problem is on your side, our side, or the target's side.
{
"success": false,
"error": "Target returned 403 after solver attempts",
"metadata": { "elapsed_time": 12.4, "solver_used": true }
}
Status codes
| Code | Meaning | What to do |
|---|---|---|
200 | Success | Read data. Still check data.status_code. |
400 | Bad request | A parameter is missing or invalid. Fix the body. |
401 | Unauthorized | API key missing or invalid. Check the X-API-Key header. |
402 | Payment required | Balance exhausted, trial expired, monthly quota used up, or direct proxy locked on trial. Check the code field. |
429 | Too many requests | Concurrency limit (CONCURRENCY_LIMIT) or request-rate guard. Back off and retry. |
500 | Server error | Transient on our side. Retry with backoff. |
502 | Unlock failed | We could not unlock the target. Try js_rendering or a different proxy country. |
503 | Service unavailable | Maintenance, overload, or queue unavailable. Retry shortly. |
504 | Gateway timeout | Scrape job timed out (queue overload or very slow target). Retry with backoff. |
Structured error codes
Some 4xx responses include a machine-readable code so you can branch in code without string-matching the message:
{
"success": false,
"error": "Concurrent request limit reached for your plan. Retry shortly.",
"code": "CONCURRENCY_LIMIT"
}
code | HTTP | Meaning |
|---|---|---|
CONCURRENCY_LIMIT | 429 | Too many in-flight requests for your plan. A Retry-After header is included. Lower concurrency or upgrade. See Rate limits. |
MONTHLY_QUOTA_EXCEEDED | 402 | Legacy code for hard monthly caps. Current Startup/Growth plans bill overage at PAYG rates once included requests are used — you get a normal insufficient-balance 402 if balance cannot cover overage. |
PROXY_LOCKED | 402 | Direct proxy access (/v1/proxy/fetch or proxy credential rotation) requires PAYG or an active plan. Trial users should use Web Unlocker or BaaS, or activate PAYG ($5 minimum top-up). |
TRIAL_EXPIRED | 402 | The 7-day free trial window ended. Top up at least $5 via checkout to continue. |
TRIAL_UPGRADE_REQUIRED | 402 | Trial accounts cannot switch to PAYG from the plan picker alone — complete a $5 minimum top-up first. |
MIN_TOPUP | 400 | Checkout amount is below the $5 minimum (or above the $500 maximum). |
Some 429 responses have no code field — e.g. "Rate limit exceeded. Please slow down." when you send requests faster than the per-account request-rate guard (~20 req/s). Back off briefly and retry. See Rate limits.
Two layers of status
There are two status codes to watch:
- The HTTP status of the OmniScrape API call (above).
200means we processed your request. - The target site's status, in
data.status_code. You can get HTTP200from us withdata.status_code: 404because the page itself returned 404.
r = resp.json()
if not r["success"]:
handle_api_error(r["error"])
elif r["data"]["status_code"] >= 400:
handle_target_error(r["data"]["status_code"])
else:
use(r["data"]["content"])
Recommended retry strategy
- Retry
429,500,502,503,504with exponential backoff (e.g. 1s, 2s, 4s, 8s). - Do not retry
400,401,402— the request will keep failing until you fix it. - For stubborn
502s, escalate the mode tojs_renderingand switch proxy country.
import time, requests
def scrape_with_retry(payload, headers, attempts=4):
for i in range(attempts):
r = requests.post(
"https://api.omniscrape.io/v1/scrape",
headers=headers, json=payload, timeout=120,
)
if r.status_code in (429, 500, 502, 503, 504) and i < attempts - 1:
time.sleep(2 ** i)
continue
return r.json()
Billing on failure
Failed core unlock requests are not billed. If success is false, expect billing.charged to be 0.