Most application routes translate known failures to a JSON object containing error (a user-facing message) and code (a machine-readable category). An ApiError can also add a bounded details object, such as account-link guidance. HTTP status and code have different purposes: status classifies the request outcome, while code selects a concrete recovery path.

json
{
  "error": "Your workspace changed. Refresh and try again.",
  "code": "revision_conflict"
}
StatusTypical meaningClient response
400Invalid object, field, enum, identifier, or cursorFix the request; repeating identical invalid input will not help.
401Missing, invalid, expired, or required identityRefresh/sign in through the correct identity provider.
403Origin mismatch or resource/role permission failureCheck app origin and current membership; do not retry with another workspace ID to evade access checks.
404Missing or inaccessible resourceRefresh resource selection; a form capability can intentionally conceal unpublished forms.
409Revision, state, dependency, or concurrency conflictReload and review the relevant state before retrying.
413Endpoint body or file size limit exceededReduce payload size within that endpoint’s limit.
415Unsupported content typeUse JSON or multipart as required by the route.
422Confirmation, rule, provider capability, form-value, or generated-layout issueResolve the specific requirement before resubmitting.
429Request/actor concurrency limitBack off, reduce parallel requests, and allow active work to finish.
500Unexpected server failureShow the safe error; use operator logs for diagnosis without exposing internals.
502 / 503Provider/service unavailable or bounded queue fullInspect the feature state and retry appropriately after recovery.

Generic server failures return a safe server_error message. Known validation errors use validation_error; confirmation-related engine errors may use 422 rather than 400. Not every endpoint emits every status or provides Retry-After. Do not invent a retry delay from a missing header.

Match the conflict to its resource#

CodeResource that must be refreshed
revision_conflictWorkspace or reviewed proposal revision
page_changedRecord paging snapshot; restart at the first page
draft_dependencies_changedSource data used by a saved AI draft; regenerate for review
invalid_draft_stateAI job state: only eligible completed/unapplied drafts can be edited
resume_context_changedSaved failed-job request/context; preserve exact input or start a new request
email_draft_changedLatest recipients/body and updatedAt for an email draft
email_categories_conflict / email_categories_staleThe actor’s separate category revision
email_sync_busyAn actively updating mailbox; wait before organizing it again

Retry identities belong to specific operations#

Durable AI creation uses clientRequestId; email draft creation and sending use their own clientRequestId values; template installation uses requestId; public forms use submissionId. These are not interchangeable or universal idempotency headers. Preserve a logical request’s key across a lost response, and create a new key only for a new intended operation. Reusing a key with changed content can be rejected.

HTTP 202 on email sending means queued, not delivered. Draft statuses include queued, preparing, sending, sent, unknown, and failed. An unknown outcome needs reconciliation against provider evidence; blindly constructing a new send request can create a duplicate message. Likewise, cancellation of an AI request should be judged by returned/saved state rather than a closed browser connection.

Account for format exceptions#

  • OAuth callbacks return 303 redirects with bounded result query parameters even on failure.
  • Download routes return bytes on success but can return a JSON error body; inspect status before writing a file.
  • NDJSON interpretation can emit an error event after response streaming has begun; HTTP status alone cannot describe that late failure.
  • MCP auth failures can use JSON-RPC errors and WWW-Authenticate; early host/origin guards have their own response. A tool-level isError result differs from a failed HTTP request.
  • AI availability can be expressed as a proposal with no operations and aiUnavailable even when the immediate interpretation HTTP response is 200.