API ReferenceError shapes

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 for hint / reason / suggestion / quota before 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"
  }
}
FieldTypeAlways presentMeaning
errorstringyesShort human-readable summary. Safe to surface directly. The only field guaranteed on every non-2xx response.
hintstringnoA 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.
reasonstringnoTwo distinct uses depending on surface — see reason has two meanings below.
reason_detailstringnoThe human-readable explanation when reason is the machine code. Only on site.revision_failed webhook payloads, not HTTP bodies.
suggestionstringnoAn 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.
quotaobjectno{ used, limit, resetDescription }. Seen on 429s. Surface resetDescription (“resets at midnight UTC”, etc.) in your UI rather than your own copy.
queue_fullbooleannoSeen 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_errorstringnoA 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:

SurfaceWhat 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 payloadThe 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

StatusTypical envelopeNotes
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.

EndpointNon-standard fieldsReference
POST /v1/siteshint (share.google rejection, unresolvable mapsUrl); quota on 429Errors
POST /v1/sites/:id/revisionsreason, suggestion on 422 out_of_scope; queue_full on 429; quota on 429Errors
POST /v1/sites/:id/approve-researchquota on 429per-page Errors
PATCH /v1/sites/:id/research-draftstandard { error }per-page Errors
DELETE /v1/sites/:id/domains/:domaininternal_error on 500/502 partial-failure pathsErrors
POST /v1/sites/:id/domainsstandard { error }per-page Errors
POST /v1/domains/checkstandard { error }per-page Errors
POST /v1/domains/registerstandard { error }per-page Errors
POST /v1/sites/:id/refreshstandard { error }per-page Errors
GET /v1/sites/:id/exportquota 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,
  })
}