DocsGuide

Errors

One envelope on every surface, a fixed set of codes, and what to do with each.

The envelope

{
  "code": "bad_request",
  "error": "subject_id is required",
  "details": [{ "type": "field", "field": "subject_id", "description": "is required" }]
}

code is a stable slug from the table below and the HTTP status follows it; error is a sentence for a person; details are typed entries. The field names and the slugs are frozen: a client is written against them.

codeHTTPWhat to do
bad_request400fix the request; a field detail names the field
failed_precondition400the resource is in a state that refuses this call
unauthenticated401no token, or an expired one: fetch a new token
forbidden403the token is valid and this route is not yours
not_found404no such route or resource
already_exists409the thing you created exists
conflict409the call raced another; read and retry
payload_too_large413the body is over 2 MiB
unsupported_media_type415the body is not application/json
rate_limited429wait Retry-After seconds; a retry detail carries it too
quota_exceeded429the account's units for the month are used up; see Usage and units
cancelled499you cancelled
internal500our fault; the sentence is internal error and the cause is in our logs under the request's trace
unimplemented501the route exists and does nothing yet
unavailable503a backend is down; retry with backoff
timeout504a backend was too slow; retry with backoff

Details

  • {"type":"field","field","description"}: which field and why.
  • {"type":"info","reason","domain","metadata"}: a machine-readable reason.
  • {"type":"retry","after_seconds"}: when to try again; Retry-After carries the same.

Per transport

REST sends the envelope as the body with the HTTP status. Server-sent events send it as the data of an event: error and end the stream. The WebSocket sends {"type":"error","id","code","error","details"} for a call, or without id when the frame itself was refused.