Errors & envelope
Every response — success or failure — uses the same JSON envelope, so a client only ever needs one code path for "did this work."
Success envelope
{
"success": true,
"data": { "…": "…" },
"meta": {
"request_id": "0198f3a2-…",
"units_charged": 1,
"pagination": { "next_cursor": "…", "has_more": true },
"freshness": { "corpus_last_event_at": "2026-08-01T04:12:09Z" }
}
}pagination and freshness are only present where applicable to the endpoint.
Error envelope
{
"success": false,
"error": {
"code": "quota_exceeded",
"message": "Monthly quota of 500 units used.",
"details": { "period_end": "2026-08-31", "upgrade": "https://crawlpipe.com/pricing" }
},
"meta": { "request_id": "…" }
}Check success first — never infer failure from HTTP status alone, and never parse
error.message for logic (it's for humans; branch on error.code).
Error codes
| HTTP | Code | When |
|---|---|---|
| 400 | validation_error | Bad params — see details.fields |
| 401 | invalid_key | Missing, unknown, or revoked API key |
| 402 | payment_required | x402 endpoint without payment, or a past-due subscription |
| 403 | feature_not_in_plan | e.g. semantic search on the Free plan — see details.required_plan |
| 403 | insufficient_scope | Key lacks the required scope — see details.required_scope |
| 404 | not_found | No matching resource |
| 409 | idempotency_conflict | Same Idempotency-Key, different request body |
| 422 | unprocessable | e.g. an at= date before the document existed |
| 429 | rate_limited | Per-minute limit hit — see the Retry-After header |
| 429 | quota_exceeded | Monthly unit quota exhausted |
| 429 | spending_cap_reached | Org spending cap hit |
| 500 | internal_error | Unexpected server error — never leaks internals |
Headers on every response
X-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
(unix timestamp). Metered successes additionally return
X-CrawlPipe-Units-Charged and X-CrawlPipe-Units-Remaining — see
Metering & idempotency.