Error shapes
Every non-2xx response from the Warpweb API returns a JSON body with at least an error string. Depending on the endpoint and the failure mode, the body may carry one or more optional fields that give the caller a recovery path or machine-readable context.
This page is the canonical reference for the envelope. Per-endpoint reference pages document which optional fields THAT endpoint can emit; this page documents what each field means whenever you encounter it.
Implementation note. The current implementation emits a handful of envelope variants depending on which handler ran; we’re consolidating over time. Until then, write your error-handling code defensively: always read
error; check forhint/reason/suggestion/quotabefore using them.
Canonical envelope
{
"error": "Human-readable summary of what went wrong.",
"hint": "Optional. Recovery path or context — surface to your operator.",
"reason": "Optional. Machine-readable code for branching, OR the human-readable reason on /revisions 422.",
"suggestion": "Optional. An in-scope alternative the caller can try.",
"quota": {
"used": 5,
"limit": 5,
"resetDescription": "resets at midnight UTC"
}
}| Field | Type | Always present | Meaning |
|---|---|---|---|
error | string | yes | Short human-readable summary. Safe to surface directly. The only field guaranteed on every non-2xx response. |
hint | string | no | A specific recovery path the operator can take. When present, prefer surfacing hint over error because it tells the operator what to DO next. Seen on POST /v1/sites for share.google rejection + unresolvable mapsUrl. |
reason | string | no | Two distinct uses depending on surface — see reason has two meanings below. |
reason_detail | string | no | The human-readable explanation when reason is the machine code. Only on site.revision_failed webhook payloads, not HTTP bodies. |
suggestion | string | no | An in-scope alternative when a request was refused. Seen on POST /v1/sites/:id/revisions 422 when the agent rules a request out of scope — surface this to your end user so they can re-ask with a feasible variation. |
quota | object | no | { used, limit, resetDescription }. Seen on 429s. Surface resetDescription (“resets at midnight UTC”, etc.) in your UI rather than your own copy. |
queue_full | boolean | no | Seen on the per-site revision queue cap (429). true when 3 revisions are already running/waiting for this site — back off and retry once the current revision lands. |
internal_error | string | no | A lower-level error message. Returned on partial-failure paths where the operator-facing fix differs from the underlying cause (e.g. Cloudflare un-bind succeeded but DB tier flip failed). Useful for support tickets; not for end-user surfaces. |
reason has two meanings
The reason field is overloaded across surfaces. Branch by where you’re reading it from:
| Surface | What reason holds |
|---|---|
HTTP body, POST /v1/sites/:id/revisions 422 ({ error: "out_of_scope" }) | The human-readable explanation of why the agent ruled the request out of scope. Surface to your user alongside suggestion. |
site.revision_failed webhook payload | The machine-readable code (out_of_scope, typecheck_iteration_cap, sdk_error, queue_timeout, infrastructure, cancelled, …). For human-readable text on this surface, read reason_detail. |
This asymmetry is documented on POST /v1/sites/:id/revisions. For code that reads both surfaces, branch by surface first, then by reason.
Status code map
| Status | Typical envelope | Notes |
|---|---|---|
| 400 | { error } or { error, hint } | Malformed body, invalid parameters, share.google URL rejection. The hint variant on POST /v1/sites carries the recovery path inline. |
| 401 | { error } | Missing or invalid API key. |
| 402 | { error: "PAYMENT_REQUIRED", ... } | Balance is 0 and auto-refill is off. Top up at app.warpweb.ai/billing. |
| 403 | { error } | Authenticated but not authorized for this resource. |
| 404 | { error: "Site not found" } (or similar) | Resource not found. Also returned for resources you don’t own — we don’t leak existence. |
| 422 | { error, reason, suggestion } (revisions) or { error } | Validation failed, or the agent ruled the request out of scope. The 3-field envelope is currently revisions-only. |
| 429 | { error, quota? } or { error, queue_full: true } | Rate-limit or quota exceeded. quota carries { used, limit, resetDescription }; queue_full is the per-site revision-queue cap. |
| 500 | { error, internal_error? } | Unexpected server error. Safe to retry with exponential backoff. |
| 502 | { error, internal_error? } | Upstream (DNS / CDN / Cloudflare) failure. Safe to retry. |
| 503 | { error } | Temporary maintenance, or a registrar transient (rate limit / WHOIS rejection). |
Endpoints that emit non-standard fields
Use this table to jump to the per-endpoint page when you’re handling a specific 4xx/5xx body shape.
| Endpoint | Non-standard fields | Reference |
|---|---|---|
POST /v1/sites | hint (share.google rejection, unresolvable mapsUrl); quota on 429 | Errors |
POST /v1/sites/:id/revisions | reason, suggestion on 422 out_of_scope; queue_full on 429; quota on 429 | Errors |
POST /v1/sites/:id/approve-research | quota on 429 | per-page Errors |
PATCH /v1/sites/:id/research-draft | standard { error } | per-page Errors |
DELETE /v1/sites/:id/domains/:domain | internal_error on 500/502 partial-failure paths | Errors |
POST /v1/sites/:id/domains | standard { error } | per-page Errors |
POST /v1/domains/check | standard { error } | per-page Errors |
POST /v1/domains/register | standard { error } | per-page Errors |
POST /v1/sites/:id/refresh | standard { error } | per-page Errors |
GET /v1/sites/:id/export | quota on 429 (10/day per site); status on 409 (site not deployed yet) | Errors |
Defensive parsing pattern
type WarpwebError = {
error: string
hint?: string
reason?: string
reason_detail?: string
suggestion?: string
quota?: { used: number; limit: number; resetDescription: string }
queue_full?: boolean
internal_error?: string
}
async function callWarpweb(input: RequestInfo, init?: RequestInit) {
const res = await fetch(input, init)
if (res.ok) return res.json()
const body: WarpwebError = await res.json().catch(() => ({ error: `HTTP ${res.status}` }))
// Prefer the most specific recovery path the server offered.
const userMessage = body.suggestion ?? body.hint ?? body.error
throw Object.assign(new Error(userMessage), {
status: res.status,
body,
})
}Related
API Reference → Common conventions → Errors— the status-code table.Authentication → Rate limits— what triggers 429s and how thequotashape is composed.