Skip to main content
Every error carries a stable machine-readable code, a human-readable message, and a correlation token:

Which errors to retry

internal_error can be deterministic, so a blind retry can loop on the same failure. Retry a publish only when it carries an idempotency_key; without one you risk storing the message twice.

Backing off

retry_after_ms and the RFC 9110 Retry-After header carry the same hint. Either one wins over whatever schedule your client uses. When the server sends no hint, back off exponentially with jitter, cap the delay, and stop after a small number of attempts. Cap any hint you honour, server-supplied or not, so a bad value cannot park a request for hours.

Correlating a failure

Every response, success or failure, carries an X-Request-Id header. Error bodies repeat the same value as request_id so it survives client-side logging that drops headers. Log request_id on every failure and quote it to support. It pivots directly to the corresponding server log line. Inbound X-Request-Id headers are ignored; the server assigns the value. Send X-Amzn-Trace-Id and its Root= segment becomes the request id, so upstream trace correlation works without translation.

Malformed requests fail loudly

Request bodies decode strictly. Each of these returns 400 invalid_request:
  • An empty body, or a body that is not valid JSON
  • A field this API does not define, including a misspelled one
  • Trailing content after the first JSON value
An unknown field is an error rather than being ignored, so {"namesapce": "articles"} fails immediately instead of silently falling back to a default that does not exist.

Checking a query before you send it

POST /v1/lint reports the same query problems as diagnostics with "valid": false and a 200, needs no API key, and embeds nothing. It is the cheap way to check a generated query before spending a request on it.