# Errors and retries

Branch on HTTP status and stable error code, preserve unknown codes, honor Retry-After, and avoid replaying ambiguous non-idempotent mutations.

## Public error contract

| HTTP | Code | Meaning |
| --- | --- | --- |
| 400 | invalid_request | JSON, fields, or values are invalid. |
| 401 | unauthorized | The bearer key is missing, invalid, or revoked. |
| 404 | not_found | The API path is unknown, or the scoped notification or source does not exist. |
| 409 | conflict | The requested metadata mutation conflicts with current state. |
| 409 | quota_exceeded | The source cannot store another notification. |
| 413 | payload_too_large | The raw HTTP body exceeds the request limit. |
| 429 | rate_limited | The shared per-key request window is exhausted. |
| 500 | internal_error | The server could not complete the request. |

Errors use { error, message? }. Clients should retain unknown non-empty error codes so the service can add a new code without turning a parseable failure into an opaque one.

Authenticated source-key responses include RateLimit-Policy ("source-key";q=<requests>;w=60) and RateLimit ("source-key";r=<remaining>;t=<seconds>). The quota is shared by all public API operations using the same key. Values reflect the request just processed; r is the remaining request count and t is seconds until that window resets. A 429 also includes Retry-After; honor that delay before retrying. A 401 has no per-key counter because the key was not authenticated.

## Retry policy

1. **Validate locally.** Fix 400, 401, 404, 409, and 413 responses before sending the same operation again.
2. **Honor rate limiting.** For 429, wait for Retry-After when the delay fits your total deadline, then retry conservatively.
3. **Treat ambiguity explicitly.** A notification read is safe to retry. A network failure or 5xx may happen after a write commits: retry create only with a stable deduplicationKey, and do not automatically replay metadata mutations.
4. **Keep diagnostics safe.** Record status, stable error code, and X-Request-ID when present, but never log the bearer key or full sensitive payload.
