> ## Documentation Index
> Fetch the complete documentation index at: https://docs.peeng.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Peeng API Error Reference: Status Codes and Retries

> Every Peeng API error shares the same JSON envelope. Learn what each HTTP status code means and which failures are safe to retry with backoff.

Every error response from the Peeng API — whether it is a validation failure, an authentication problem, or an internal server error — uses the same JSON envelope shape. Consistent error structure means you can write a single error-handling path in your code and branch on `statusCode` or `error` for any special cases.

```json theme={null}
{
  "statusCode": 400,
  "message": "level must be one of the following values: debug, info, warn, error, fatal",
  "error": "Bad Request"
}
```

The three fields are always present: `statusCode` mirrors the HTTP status code, `error` is a machine-readable error class, and `message` is a human-readable explanation of what went wrong.

<Note>
  On `POST /v1/logs` validation failures, `message` can be an **array of strings** rather than a single string — one message per failed field, across all entries in the batch. Since a single request can carry up to 500 log entries, you may receive up to 500 validation messages in one response.
</Note>

## Status codes

| Status | Meaning                                                                               | Retry?                                                         |
| ------ | ------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `400`  | Validation failed — a field is missing, the wrong type, or an out-of-range value.     | **No** — fix the payload before retrying.                      |
| `401`  | Missing or invalid `x-api-key` header.                                                | **No** — fix the API key before retrying.                      |
| `402`  | Monthly ingestion limit reached (`error: "PlanLimitExceeded"`).                       | **No** — upgrade your plan or wait for the next billing cycle. |
| `409`  | The `Idempotency-Key` you sent is still being processed by another in-flight request. | **Yes** — back off briefly and retry.                          |
| `429`  | Rate limit exceeded. See [Rate Limits](/essentials/rate-limits).                      | **Yes** — respect `Retry-After` if present.                    |
| `5xx`  | Something went wrong on Peeng's side.                                                 | **Yes** — use exponential backoff.                             |

## Retryable vs. non-retryable

Both the JavaScript and Python SDKs apply the following rule automatically, and you should mirror it when calling the HTTP API directly.

**Retryable errors** — the SDKs retry with exponential backoff plus jitter, up to `maxRetries`, and reuse the same `Idempotency-Key` so a successful retry can never double-insert logs:

* `429 Too Many Requests`
* Any `5xx` server error
* Network-level failures: DNS resolution failures, connection refused, and request timeouts

**Non-retryable errors** — the SDKs fail fast and surface the failure immediately via the `onError` callback, without consuming any retry budget:

* `400 Bad Request` — retrying a malformed batch will never succeed; fix the payload instead
* `401 Unauthorized` — retrying with a bad or missing API key will never succeed; fix the key instead
* Other `4xx` responses not listed above

If you are calling the HTTP API directly, apply this same distinction in your own retry logic. Retrying a non-retryable error wastes time and burns through your rate-limit quota without any chance of success.
