01Error shape
All errors share one envelope. The code field is stable and machine-readable; the message field is human-readable and may change between versions.
{
"status": "error",
"code": "rate_limited",
"message": "You hit your plan's per-second cap. Slow down or upgrade.",
"docs_url": "https://hypedata.io/docs/errors.html#ratelimit",
"retryable": true,
"retry_after_s": 2,
"trace_id": "trc_3F2D1A77B0E1",
"detail": { /* code-specific extras */ }
}
- retryable — boolean. If
true, your client should retry with backoff. SDKs do this automatically up to maxRetries.
- retry_after_s — present on 429 and 503. Matches the
Retry-After header.
- trace_id — paste this into a support ticket and we can find the exact request in our logs within seconds.
02Auth · 401 / 403
| HTTP | Code | Retry | Meaning |
| 401 | missing_credentials | No | No Authorization header sent. |
| 401 | malformed_token | No | Header present but token doesn't match hd_(live|test)_*. |
| 401 | unknown_key | No | Token shape valid but no such key. |
| 401 | revoked | No | The key was revoked. detail.revoked_at tells you when. |
| 403 | unauthorized_ip | No | Source IP not on the key's allow-list. |
| 403 | insufficient_scope | No | Key lacks the scope required. detail.required lists it. |
03Validation · 400 / 422
| HTTP | Code | Retry | Meaning |
| 400 | invalid_url | No | The url isn't http(s):// or is malformed. |
| 400 | conflict_params | No | Mutually exclusive parameters set. detail.fields names which. |
| 400 | body_too_large | No | Request body exceeded 25 MB. |
| 422 | schema_invalid | No | Your extract object isn't a valid sketch or JSON Schema. |
| 422 | validation_failed | No | Parser output didn't satisfy your strict JSON Schema. detail.partial_data is available. |
04Billing · 402
| HTTP | Code | Retry | Meaning |
| 402 | workspace_balance | No | Workspace is out of credits. Top up to resume. |
| 402 | key_ceiling_exceeded | No | This key's lifetime credit cap was hit. Raise it or use another key. |
| 402 | card_declined | Maybe | Auto top-up failed. Update the payment method and retry. |
| 402 | subscription_past_due | No | Workspace's monthly invoice is past due. Settle in billing. |
05Rate limit · 429
| HTTP | Code | Retry | Meaning |
| 429 | rate_limited | Yes | You hit a per-second / per-minute cap. retry_after_s tells you when. |
| 429 | concurrency_limit | Yes | Too many concurrent streams or jobs. Wait for one to finish. |
| 429 | fair_use_trim | Yes | Sustained high load on a hard target — we're throttling to stay polite. See Rate limits. |
06Acceptable use · 451
| HTTP | Code | Retry | Meaning |
| 451 | aup_violation | No | Target is on our block-list (e.g. CSAM, sanctioned domains). See Acceptable Use. |
| 451 | geo_restricted | No | Source jurisdiction prohibited from accessing the target. Try a different proxy region. |
07Target failures · 5xx pass-through
These are conditions about the upstream site, not Hypedata. We surface them so your code can decide whether to retry.
| HTTP | Code | Retry | Meaning |
| 504 | upstream_timeout | Yes | The target didn't respond in time. Free retry. |
| 502 | upstream_bad_gateway | Yes | Target returned a transient 5xx. Free retry. |
| 521 | upstream_down | Yes | Target refused all connections. Retry after a longer backoff. |
| 522 | blocked | Maybe | Anti-bot wall after exhausting our internal retries. Try proxy_type: "residential" or "mobile". |
| 523 | captcha | Maybe | Target served a captcha challenge we couldn't solve. Try residential or a session. |
| 410 | upstream_gone | No | Target returned 410 Gone. The URL is permanently dead. |
09Handling strategy
A minimal but production-grade error handler:
try {
const page = await hd.scrape({ url });
await save(page);
} catch (e) {
if (e.code === "workspace_balance") page("#oncall");
else if (e.code === "blocked") await retryWithResidential(url);
else if (e.retryable) await queue.retry(url, e.retry_after_s);
else await deadLetter(url, e);
}
Three rules:
- Always branch on
code, never on message.
- Always honor
retry_after_s — clipping it produces 429 storms that get you trimmed.
- Always capture
trace_id into your logs. It's the only field that makes a support ticket actionable.