Appearance
Responses
Ingest answers every request with one of a handful of statuses. Your client only has to know one rule:
Rule of thumb
Anything not answered 202 was not stored by us, so keep it and retry.
/v1/ingest
| Status | Body | Meaning | Your client should |
|---|---|---|---|
202 | {"accepted":"<id>"} | Durably stored. The only success | Delete the batch from its outbox |
401 | {"error":"invalid_key"} | Unknown, revoked or brand-new key | Pause sending, report a configuration error, keep the outbox, and re-check every ~10 minutes and on the next launch |
413 | {"error":"payload_too_large","max_bytes":524288} | The body is over 512 KB as sent | Split the batch and resend. Never resend it unchanged |
408 | {"error":"request_timeout"} | The body didn't finish arriving within 10 seconds | Retry |
429 | {"error":"rate_limited"} + Retry-After | This key is over its rate limit, or your address sent too many unknown keys this second | Retry after Retry-After |
429 | {"error":"over_quota"} + Retry-After: 3600 | Your organization sent twice its monthly quota | Keep the outbox and retry after Retry-After. An upgrade or the next month lifts it |
503 | {"error":"busy"} + Retry-After | Ingest is overloaded or storage is unavailable | Retry after Retry-After |
415 | {"error":"unsupported_content_encoding"} | A Content-Encoding other than gzip or identity | Fix the client. Never resend unchanged |
| Anything else, a timeout or a network error | — | Unknown whether it was stored | Retry. event_id makes any copy harmless |
401: a new key takes a few seconds
A key you just created works within a few seconds. Until then it may answer 401. That is why a client should pause and re-probe on 401 rather than drop data: the first re-probe usually succeeds.
413: split, don't drop
Halve the batch and send each half. Repeat until it fits. A single event that is still too large can never be accepted, and is the only thing a client may drop.
Large bodies may see a reset
Refusals of large or slow bodies close the connection, so a client streaming far past 512 KB may see a connection reset instead of the 413. Keep requests under 256 KB and you never will.
429 and 503: honor Retry-After
Retry-After is in seconds. Wait at least that long, then retry with your usual backoff. See Writing a sender.
/v1/logs (OpenTelemetry)
/openrouter/v1/traces answers the same way.
OTLP answers follow the OTLP/HTTP spec, in the request's encoding: protobuf for application/x-protobuf, JSON otherwise.
- Success is
200with an emptyExportLogsServiceResponse: an empty protobuf body, or{}in JSON. Like202, it is sent only once the body is stored. - Errors carry a
google.rpc.Status, such as{"code":16,"message":"invalid ingest key; …"}.
| Status | gRPC code | When | OpenTelemetry exporters |
|---|---|---|---|
401 | 16 UNAUTHENTICATED | Unknown, revoked or not-yet-published key | Drop the batch |
413 | 3 INVALID_ARGUMENT | Body over 512 KB as sent | Drop the batch |
415 | 3 INVALID_ARGUMENT | An unsupported Content-Type or Content-Encoding | Drop the batch |
400 | 3 INVALID_ARGUMENT | The body couldn't be read | Drop the batch |
429 + Retry-After | 8 RESOURCE_EXHAUSTED | This key is over its limit | Retry |
503 + Retry-After | 14 UNAVAILABLE | Overloaded, storage unavailable, or the body didn't arrive within 10 seconds | Retry |
404 | 12 UNIMPLEMENTED | /v1/traces or /v1/metrics; the message names the setting to turn off | Drop, and log the message |
Exporters drop on 401 and 413
OTLP exporters retry only 429, 502, 503 and 504. A wrong key or an oversized export loses that batch, so check the key before going live, and keep exports under 512 KB (see OpenTelemetry).
Segment paths
- Success is
200 {"success":true}, sent only once the body is stored. - Errors use
/v1/ingest's statuses andRetry-After, with the body{"success":false,"error":"<name>","code":"<name>","message":"<name, spaced>"}. A413carries nomax_bytes. - A Content-Type other than
application/jsonortext/plain, or none at all, is415 unsupported_content_type.
Sentry, Datadog, Loki and Splunk
Each drop-in answers in its own protocol, with /v1/ingest's statuses and Retry-After unless noted. Success is sent only once the body is stored.
| Protocol | Success | Errors | Differences |
|---|---|---|---|
| Sentry | 200 {} | {"detail":"…"} | Every answer carries X-Sentry-Rate-Limits naming the item types we don't store, so the SDK stops sending them. A 429 adds <seconds>::key |
| Datadog logs | 202 {} | {"errors":["…"]} | An unknown key is 403, as Datadog answers |
| Loki | 204 | Plain text | A slow body is 503 + Retry-After |
| Splunk HEC | 200 {"text":"Success","code":0} | {"text":"…","code":<HEC code>} | No key 401 code 2, a wrong key 403 code 4, 429 and 503 code 9, unreadable or too large code 6 |
Vercel and Heroku drains
- Success is
200 {"accepted":"<id>"}, sent only once the whole delivery is stored. - Errors use
/v1/ingest's statuses, bodies andRetry-After. - Vercel only:
403 {"error":"missing_signature"}or403 {"error":"invalid_signature"}whenx-vercel-signaturedoesn't match the ingest key.
Next steps
- Writing a sender: batching, the outbox and backoff, built on these answers.
- Troubleshooting: what each answer usually means in practice.