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

# Error Handling

> Understand Praeto Dispatcher error responses and recovery behavior.

# Error Handling

Praeto Dispatcher uses JSON error responses.

Standard shape:

```json theme={null}
{
  "ok": false,
  "error": "error message",
  "details": {}
}
```

`details` may be a string, object, array, or omitted depending on the failure.

***

## Common HTTP statuses

| Status | Meaning                        | Typical Cause                                                |
| -----: | ------------------------------ | ------------------------------------------------------------ |
|  `400` | Bad request                    | Invalid JSON, invalid URL, missing fields, payload too large |
|  `401` | Unauthorized                   | Missing/invalid bearer token                                 |
|  `404` | Not found                      | Event, endpoint, or delivery does not exist for this tenant  |
|  `409` | Conflict                       | Idempotency key reused with different payload                |
|  `429` | Rate limited or quota exceeded | Too many requests or monthly/hard limit reached              |
|  `500` | Internal server error          | Unexpected backend/database/runtime failure                  |

***

## Authentication errors

Missing bearer token:

```json theme={null}
{
  "ok": false,
  "error": "missing bearer token"
}
```

Invalid API key:

```json theme={null}
{
  "ok": false,
  "error": "invalid API key"
}
```

Invalid admin key:

```json theme={null}
{
  "ok": false,
  "error": "invalid admin key"
}
```

***

## Idempotency conflict

Returned when the same `Idempotency-Key` is reused with a different payload.

```json theme={null}
{
  "ok": false,
  "error": "idempotency key already used with a different payload",
  "details": {
    "event_id": "811fad9a-d2cb-4dd2-a2e1-9bb5d90190db"
  }
}
```

Correct response from the publisher:

* Do not retry with a different payload under the same key.
* Fetch or inspect the original event.
* Use a new idempotency key only if this is genuinely a different event.

***

## Rate limit errors

Example:

```json theme={null}
{
  "ok": false,
  "error": "rate limit exceeded",
  "details": {
    "tenant_id": "default",
    "action": "events:write",
    "limit": 60,
    "current": 61,
    "window_seconds": 60,
    "window_start": "2026-04-28T09:14:00.000Z",
    "retry_after_seconds": 16
  }
}
```

Recommended client behavior:

1. Read `details.retry_after_seconds`.
2. Wait at least that long.
3. Retry with the same `Idempotency-Key` if retrying `POST /v1/events`.

***

## Quota limit errors

Possible quota failures:

* monthly event limit exceeded
* monthly attempt limit exceeded
* max endpoint count exceeded
* max fan-out endpoints exceeded
* payload too large

Recommended response:

* inspect `/v1/usage/current`
* inspect `/v1/usage/limits`
* upgrade plan or reduce usage

***

## URL validation errors

Praeto Dispatcher blocks unsafe webhook URLs.

Examples:

```json theme={null}
{
  "ok": false,
  "error": "invalid url",
  "details": "url hostname is blocked for SSRF protection"
}
```

```json theme={null}
{
  "ok": false,
  "error": "invalid url",
  "details": "url must not include username or password credentials"
}
```

```json theme={null}
{
  "ok": false,
  "error": "invalid url",
  "details": "url must start with http:// or https://"
}
```

Blocked examples:

```text theme={null}
http://localhost:8080/webhook
http://127.0.0.1:8080/webhook
http://10.0.0.5/webhook
http://172.16.0.1/webhook
http://192.168.1.20/webhook
http://169.254.169.254/latest/meta-data
http://metadata.google.internal/computeMetadata/v1
ftp://example.com/webhook
https://user:pass@example.com/webhook
```

***

## Delivery failure vs API failure

Important distinction:

* `POST /v1/events` returning success means Praeto accepted and queued the event.
* It does not guarantee every downstream webhook endpoint returned success.

Use:

```http theme={null}
GET /v1/events/{event_id}/deliveries
GET /v1/deliveries/{delivery_id}/attempts
GET /v1/endpoints/{endpoint_id}/health
```

for delivery status.

***

## Retry-safe client behavior

When publishing events:

1. Always send an `Idempotency-Key`.
2. If the request times out or returns `5xx`, retry with the same key and same body.
3. If the response is `409`, stop and inspect the original event.
4. If the response is `429`, wait for `retry_after_seconds` and retry with the same key.
