Skip to article

Core concepts

Errors

The error envelope, the codes you will meet, and how to handle each one.

Every failure returns the same envelope. This page is a placeholder for the error contract.

The envelope#

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests",
        "requestId": "req_01H..."
    }
}

Codes#

CodeStatusMeaning
invalid_request400The body or query failed validation
unauthorized401The key is missing, malformed or revoked
forbidden403The key lacks the required scope
not_found404The resource does not exist in this tenant
conflict409The write conflicts with current state
rate_limited429Too many requests; retry after the delay
internal_error500An unexpected failure; safe to retry

Always log the request id

The requestId is the fastest way to correlate a failure with server logs. Include it in every error report.

Retrying#

Retry a 429 or a 5xx with exponential backoff. Do not retry a 4xx other than 429: the request will fail the same way.

async function withRetry<T>(run: () => Promise<T>, attempts = 4): Promise<T> {
    for (let attempt = 0; attempt < attempts; attempt += 1) {
        try {
            return await run();
        } catch (error) {
            if (attempt === attempts - 1) throw error;
            await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 100));
        }
    }
    throw new Error("unreachable");
}

Idempotency keys#

For a retried write, send an Idempotency-Key header. The server replays the original response instead of applying the write twice.