Skip to main content
Every refusal is a JSON object with an error member, at the HTTP status the code maps to. The status is the part to branch on; the code is the part to branch within.

Which errors to retry

paused and upstream_unavailable can share a status on the same operation. The code member is what tells them apart, so branch on the status first and on the code within it. unavailable is 501 rather than 500 so that a retry policy written against statuses does not retry it.

Only 401 means the key is the problem

Four codes can look like a key problem, and only one of them is. Treat 401 as “this key doesn’t work”: stop presenting it. forbidden_scope, not_billable and upstream_unavailable all mean the key itself is fine: it reaches no agent here, or the account cannot currently incur usage, or something the request needed did not answer. Reacting to those by discarding the key throws away a working credential and gets no further. WWW-Authenticate is sent with 401 and with no other status, so its presence is a reliable test for “the credential is the problem” if that’s easier to branch on than the status code. upstream_unavailable is the one worth retrying. It is transient by definition, and the request had no effect.

Limits

A body over either limit is refused as invalid with the same JSON envelope as any other refusal, not a bare transport error, so one error path covers size and shape alike.

Concurrency

An operation that changes a stored object takes a version and returns the new one. Supplying the version you read is what makes an update a compare-and-swap. Omitting version for an object that already exists is precondition_failed, the same as a lost race, and both carry version, the value to retry with. There is one exception without compare-and-swap: message.update changes read state, labels and folder without a version, and its labels replace the message’s labels whole, so two agents labelling one message at once keep only the last write.

Idempotency

send and the create, update and delete operations accept idempotency_key. A repeat with the same key returns the first attempt’s result rather than doing the work again. A repeat that arrives while the first attempt is still running is refused as rate_limited, with retry_after_ms saying when to ask again. A key belongs to the agent that sent it, and a refused or failed attempt does not spend it. Outside send, a key can be forgotten sooner: after a service interruption, or while the service is under heavy load. A forgotten key does the work again, exactly as if no key had been sent; it never returns the result of a different request. Treat the key as protection against the retry you make straight after a timeout, and make a retried create safe to repeat: read before retrying, or delete the extra object. A key names one request. Reusing it for a different request is refused as invalid, on create, update and delete.

Correlating a failure

Every response, successful or not, carries an X-Request-Id header, and a refusal repeats the same value as error.request_id. Quote it in a support request: it correlates the refusal with everything the request caused.