Errors and limits
Every status and error code /notify can return, what caused it and what to do next, plus the documented request, rate and plan limits.
On this page
Errors come back as JSON with an error code. Branch on the code, not on the status alone:
responses with the same HTTP status can need different handling. An error also carries a
requestId; validation failures can name the field in path, the rule in reason and its limit.
{ "error": "INVALID_TOKEN" }Error codes
Link to this section| Status | Code | What happened | What to do | Retry |
|---|---|---|---|---|
400 |
INVALID_PAYLOAD |
Malformed JSON or form data, a field that breaks its rules, an unsupported image, data over 4 KiB, or a generated push over 3,800 bytes. |
Fix the request. | No |
400 |
INVALID_IDEMPOTENCY_KEY |
The Idempotency-Key header is malformed. (A request without the header is accepted with a derived key.) |
Send a stable key of 8 to 128 allowed characters, or leave the header out. | No |
401 |
INVALID_TOKEN |
The token is missing, malformed, unknown, paused, expired or revoked. | Check the key in Sources, or create a new one. | No |
403 |
ATTACHMENTS_DISABLED |
Image attachments are temporarily switched off. | Send the event without images, using a new idempotency key. | No |
403 |
CRITICAL_SEVERITY_DISABLED |
Critical alerts are temporarily switched off. | Use another severity only if it truthfully describes the event; otherwise wait. | No |
403 |
TODO_DISABLED |
Checklists are switched off, and the request carried todo. |
Send the alert without todo if it is still useful on its own. |
No |
403 |
PLAN_REQUIRED |
The plan’s daily image budget is set to zero, or a running agent update came from an account below Zona Plus. | Send the alert without images, or stop sending running updates. | No |
404 |
CHECKIN_NOT_FOUND |
A check-in names no check on this source. | Create the check-in in the app, or correct its name. | No |
405 |
METHOD_NOT_ALLOWED |
The request used a method other than POST. |
Use POST. |
No |
409 |
IDEMPOTENCY_CONFLICT |
This source already used the key with different content or images. | Resend the original content, or use a new key for a genuinely new event. | No |
413 |
PAYLOAD_TOO_LARGE |
The JSON body was over 16 KiB, or the multipart request was over the image budget plus 64 KiB. | Send less: shorter data, smaller or fewer images. |
No |
423 |
ACCOUNT_INACTIVE |
The account that owns the source is deleted, being deleted, suspended or otherwise inactive. | Do not retry until the account is restored or support has resolved it. | No |
429 |
RATE_LIMITED |
This source went over its accepted-requests-per-minute limit. | Wait for Retry-After, then retry with the same key. |
Yes |
429 |
ACCOUNT_RATE_LIMITED |
All sources together reached the account’s alert rate, or check-in reports or running updates reached their separate 15-minute budget. | Wait for Retry-After, then retry with the same key. |
Yes |
429 |
DAILY_LIMIT_REACHED |
The account took its plan’s alerts for the day (UTC): 50 guest, 300 Standard, 3,000 Zona Plus. | Wait for Retry-After, the seconds until 00:00 UTC; a sooner retry is refused. |
Yes |
500 |
INTERNAL_ERROR |
Zona could not confirm the alert was accepted. | Back off with jitter and retry with the same key and content. | Yes |
503 |
SERVICE_UNAVAILABLE |
Alert intake is temporarily paused by a fail-closed service switch. | Wait for Retry-After, then retry with the same key and content. |
Yes |
Retry-After on 429 and 503 is a whole number of seconds. Retry network failures, 429 and
5xx only, always with the original key, body and images. The full retry policy, with code, is in
Idempotency and retries.
Limits
Link to this sectionLimits are set per plan, and the operator can tune them without an API change, so live values can differ from the documented defaults below.
Request
Link to this section| Limit | Value |
|---|---|
| JSON request body | 16 KiB |
| Multipart request body | The image budget plus 64 KiB |
title |
1 to 120 characters |
body |
1 to 2,000 characters |
category |
1 to 80 characters when present |
severity |
low, medium, high or critical when present |
data |
A JSON object of at most 4 KiB serialized as UTF-8 |
todo |
Up to 20 entries of 1 to 200 characters |
| Images | Up to five PNG, JPEG or WebP files sharing the image budget |
| Generated push payload | 3,800 UTF-8 bytes |
| Limit | Guest | Standard | Zona Plus |
|---|---|---|---|
| Accepted alerts per source | 1 in a rolling minute | 5 in a rolling minute | 10 in a rolling minute |
| Accepted alerts per account, across all sources | 1 in a rolling minute and 5 in an hour | 5 in a rolling minute | 10 in a rolling minute |
| Accepted alerts per account per day (UTC) | 50 | 300 | 3,000 |
| Image budget per alert | 1 MiB | 5 MiB | 10 MiB |
| Image budget per day (UTC) | 1 MiB | 10 MiB | 50 MiB |
| Active sources | 1 | 3 | 10 |
| Unrevoked access keys | 1 | 10 per source, 10 across the account | 10 per source, 30 across the account |
| Registered phones | 1 | 3 | 5 |
| Inbox retention | 1 day | 7 days | 30 days |
Guest accounts also have an hourly cap on accepted alerts, so a guest can receive ACCOUNT_RATE_LIMITED well
below the standard per-minute numbers. The app’s account usage shows the limits that apply to you.
The daily caps start again at 00:00 UTC. Past the alert cap, /notify answers 429 DAILY_LIMIT_REACHED.
Images that would pass what is left of the day’s image budget are dropped, not the alert: it is still
accepted, with attachmentError: "IMAGE_BUDGET_REACHED".
Running agent updates and check-in reports each have their own counters. They use the configured per-source minute rate and separate 15-minute per-account budgets. Running updates require Zona Plus and create no alert; neither kind of report uses the ordinary alert budgets above.
Staying under the rate limit
Link to this sectionRate limits count accepted alerts, per source and per account. If a job can burst, for example one
alert per failing test, send a single summary alert with the details in body, a todo list or an
image instead. When you do get a 429, wait for Retry-After and retry with the same key; an alert
that was never accepted cannot be duplicated.