---
url: https://simpledatalogs.com/docs/api/responses.md
description: >-
  Every status code ingest answers, its body, what it means, and exactly what
  your client must do next.
---

# Responses

Ingest answers every request with one of a handful of statuses. Your client only has to know one rule:

::: tip 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.

::: warning 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](/api/sender-rules#retries).

## `/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 `200`** with an empty `ExportLogsServiceResponse`: an empty protobuf body, or `{}` in JSON. Like `202`, 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 |

::: warning 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](/integrations/opentelemetry#keep-exports-under-512-kb)).
:::

## Segment paths

* **Success is `200 {"success":true}`**, sent only once the body is stored.
* **Errors** use `/v1/ingest`'s statuses and `Retry-After`, with the body `{"success":false,"error":"<name>","code":"<name>","message":"<name, spaced>"}`. A `413` carries no `max_bytes`.
* A Content-Type other than `application/json` or `text/plain`, or none at all, is `415 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 and `Retry-After`.
* Vercel only: `403 {"error":"missing_signature"}` or `403 {"error":"invalid_signature"}` when `x-vercel-signature` doesn't match the ingest key.

## Next steps

* [Writing a sender](/api/sender-rules): batching, the outbox and backoff, built on these answers.
* [Troubleshooting](/reference/troubleshooting): what each answer usually means in practice.
