status and a detail:
status repeats the HTTP status code. detail is a human-readable explanation intended for logs and developers — it is not a stable identifier, so do not branch on the text of detail. Branch on the HTTP status, or on the typed error the SDK raises.
Status codes
400 and 422 both indicate the request will not succeed as sent. Broadly, 400 means the request itself was malformed, and 422 means it parsed correctly but violates a rule — for example a value that is well-formed but not valid for the current state of the resource.Request IDs
Every response carries anX-Request-Id header — a UUID generated per request. Log it.
When you contact support about a failed request, the request ID is the fastest way for us to find it — far quicker than a timestamp and an endpoint. The TypeScript SDK surfaces it as requestId on every error from a response.
Typed errors in the TypeScript SDK
The TypeScript SDK maps failed responses onto typed error classes, so you can branch withinstanceof rather than inspecting status codes by hand.
All of them extend
AlgunaError and carry:
message— the body’sdetail, elsetitle, elsemessage, else the HTTP status textstatusCode— the HTTP status (undefinedwhen no response arrived)requestId— theX-Request-Idof the failed responsecode— set when the body carries one (a401from the gateway sendsunauthenticated)body— the parsed response bodyheaders— the response headers, lower-cased (for exampleretry-afteron a429)
UnprocessableEntityError extends ValidationError, so one instanceof ValidationError check handles both 400 and 422.credits.check and credits.track throw RateLimitError: the credit gate stayed busy, or the ledger stayed at its limit of open holds, through every retry. Insufficient credit is not an error. Those calls return it as allowed: false.
Before it throws, the SDK retries a failed attempt twice: on 408, 429, 502, 503 and 504, on a 409 with Retry-After, and when no response arrived (a refused or reset connection, or a timed-out attempt). It waits with exponential backoff, or for Retry-After when the response has one, at most 60 s. Every request that is not a GET carries an Idempotency-Key, yours or a generated one, that its retries reuse, so the SDK never applies a write twice. events.ingest, credits.track and credits.check are the exceptions. The events in the request body identify the work, so the SDK sends no generated key for these calls and retries them without one. It still sends a key that you pass. See Writes identified by their events. Set maxRetries: 0 on the client to turn retries off.
Retrying
Retry these:429— back off exponentially before retrying.502,503and504— retry with exponential backoff and jitter, with the same idempotency key.409withRetry-After— the earlier request with that idempotency key is still in flight. WaitRetry-Afterseconds, then retry with the same key. This is safe. Do not generate a new key, or you risk creating a duplicate.- Network timeouts — you do not know whether the request was applied. Retry with the same idempotency key.
400, 401, 403, 404 or 422 without changing the request. They are deterministic and will fail identically.
A 500 is an unexpected error inside the API, and the request may have changed data before it failed. The API stores no result for a 5xx under the idempotency key, so a retry runs the request again: it does not replay a result. For this reason the SDKs do not retry a 500. If you retry one yourself, retry only a request that is safe to run twice, and send the same idempotency key.