Quick links

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" }
Table, scrolls horizontally when narrow
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 are set per plan, and the operator can tune them without an API change, so live values can differ from the documented defaults below.

Table, scrolls horizontally when narrow
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
Table, scrolls horizontally when narrow
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 section

Rate 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.