Skip to content

Errors

Error bodies, every HTTP status the API returns, and which ones to retry.

Error format

Errors use a non-2xx status and a JSON body with a detail field. For most errors it is a string:

401 response
{
  "detail": "missing or invalid API key"
}

Request validation errors (422) return detail as a list, one entry per problem, with the location of the bad field:

422 response
{
  "detail": [
    {
      "type": "missing",
      "loc": [
        "body",
        "token"
      ],
      "msg": "Field required",
      "input": {
        "days": 90
      }
    },
    {
      "type": "extra_forbidden",
      "loc": [
        "body",
        "nights"
      ],
      "msg": "Extra inputs are not permitted",
      "input": 3
    }
  ]
}

A few endpoints also return 422 with a string detail for semantic checks (for example an unsupported booking engine). Handle both shapes.

Status codes

StatusMeaningRetry?
200
Success. Charged unless the result is empty.
—
202
Accepted: a calendar job was queued (POST /v1/jobs).
—
400
Well-formed but unusable parameters (e.g. end before start, bad cursor).
No
401
Missing or invalid API key.
No
402
Not enough credits. Only once credit enforcement is on — never during the beta.
After credits renew or are added
404
Unknown property, stay window, job or request, or a key not linked to a billing account.
No
409
Idempotency-Key reused with a different body (POST /v1/jobs).
No — use a new key
422
Validation failed: missing or unknown fields, out-of-range values.
No
429
Too many requests: per-minute rate limit, or plan concurrency once enforcement is on.
Yes, with backoff
502
The upstream source failed, blocked the request or returned something unusable.
Yes, with backoff
503
A dependency is temporarily unavailable.
Yes, with backoff

Failures are free

Any 4xx or 5xx response costs 0 credits, so retrying a 502 does not double-charge you.

402 and 429

Both only block when they have to:

StatusdetailWhen
402
insufficient credits: this request costs 5, 0 available. Credits renew 2026-11-01.
Credit enforcement is on and the request costs more than your available credits. Not returned during the beta (shadow mode).
429
rate limit 60/min exceeded
Your key sent more requests in the last 60 seconds than its limit (see Rate limits).
429
concurrency limit 2 reached for the Free plan
Credit enforcement is on and you already have as many requests in flight as your plan allows.

Retrying safely

  • Retry 429, 502 and 503 with exponential backoff and jitter; there are no Retry-After headers.
  • Do not retry other 4xx errors: fix the request instead.
  • For POST /v1/jobs, retry with the same Idempotency-Key so a retry cannot create a second job.
import os
import random
import time

import requests

RETRYABLE = {429, 502, 503}

def call(method, path, **kwargs):
    url = f"https://api.scrapercompany.com{path}"
    headers = {"x-api-key": os.environ["SCRAPERCOMPANY_API_KEY"]}
    for attempt in range(5):
        response = requests.request(method, url, headers=headers, timeout=120, **kwargs)
        if response.status_code not in RETRYABLE:
            break
        time.sleep(min(2 ** attempt, 30) + random.random())
    if not response.ok:
        detail = response.json().get("detail")
        raise RuntimeError(f"{response.status_code} {detail} (request {response.headers.get('x-request-id')})")
    return response.json()

calendar = call("POST", "/v1/calendar", json={"token": "ChUIoben2Mv6-CYaCi9tLzA3czVwbjQQAQ", "days": 90})

Request IDs

Every response carries an x-request-id header (req_…). It matches the id in your request history and the request_id on ledger entries. Include it when you contact support.