Errors
One error shape for every endpoint, every code with its HTTP status, and when to retry.
Error format#
Every error has the same JSON shape: an error object with a stable code, a human-readable message, the requestId and, for validation problems, a details list. Branch on code, never on message.
⫻
422 Unprocessable Entity
- Every response, success or error, carries an
X-Request-Idheader with the same id. Include it when you ask for help. - Responses allow any origin and expose the
X-Request-IdandRetry-Afterheaders to browser code. - Responses are sent with
Cache-Control: no-store.
Error codes#
| Code | HTTP | When |
|---|---|---|
| VALIDATION_ERROR | 400 | The body breaks a rule (a connect request reports INVALID_SUBSCRIPTION instead) or is not valid JSON; the body is over 16,384 bytes (by Content-Length or actual size); the encoded notification is over 3,000 bytes; Idempotency-Key is not 1–255 characters; or an action event has no action id. |
| UNAUTHORIZED | 401 | The API key is missing, malformed, unknown or revoked, or a browser event token is invalid. |
| QUOTA_EXCEEDED | 402 | The free monthly allowance is used up and the credit balance cannot cover this send's attempts. Nothing is stored, sent or charged. |
| FORBIDDEN | 403 | A live key was used from a browser (the request carries an Origin header); call the API from your server or use a test key. Also returned when connecting a browser would go over 20 connected browsers per recipient or 100 per account. |
| PROJECT_SUSPENDED | 403 | The API key's project is suspended. Contact support. |
| NOT_FOUND | 404 | The notification is not in the key's project, or its id is not a UUID; the connect link is unknown, expired, revoked, used up or its project is suspended; the attempt of a browser event does not exist; the browser to disconnect does not exist; or the route or method does not exist (there is no 405). |
| IDEMPOTENCY_CONFLICT | 409 | The Idempotency-Key was already used with a different body. A concurrent request with the same key and body waits for the first and gets its replay instead. |
| INVALID_RECIPIENT | 422 | No recipient with that name exists in the key's project, or it has no connected browsers. Nothing is stored or charged. |
| INVALID_SUBSCRIPTION | 422 | The connect request's subscription is invalid, or its endpoint is not a supported browser push service. |
| RATE_LIMITED | 429 | Too many requests. The Retry-After header says how many seconds to wait. See rate limits. |
| INTERNAL_ERROR | 500 | Something failed on our side. The message is Something went wrong. Try again with the same Idempotency-Key. |
Validation details#
A VALIDATION_ERROR about the body lists every problem in details. Each entry has the field path (dots for nesting, such as actions.0.title) and a message. An empty path means the top level of the body, for example an unknown field.
⫻
400 VALIDATION_ERROR
NoteUnknown fields are rejected rather than ignored, so a typo fails loudly. Every field and its limits are in message options.
When to retry#
429 RATE_LIMITED: wait for theRetry-Afterseconds, then retry.500 INTERNAL_ERRORand network errors or timeouts: retry with backoff and the sameIdempotency-Key, so the notification is sent at most once. See idempotency.409 IDEMPOTENCY_CONFLICT: the key was used with a different body. Do not retry; send the original body or use a new key for a new event.- Any other
4xx: fix the request first. Retrying it unchanged gets the same error.
WarningWithout an
Idempotency-Key, retrying after a timeout can send the notification twice, because the first request may have succeeded.