Errors & rate limits
Every error code with its HTTP status, plus the request and send rate limits.
Errors return a JSON body { "error": "code", "request_id": "…" } (often with a readable message, and sometimes more fields) and a standard HTTP status. The same request_id comes back in the X-Request-Id header of every response — quote it when you contact support. Branch on error, not on message, which may change.
| Status | Code | When |
|---|---|---|
400 | invalid_json | The body isn't valid JSON (send Content-Type: application/json). |
400 | title_required, external_ids_empty, segment_not_found, unknown_topic | A send (or a draft being sent): no title; externalId / externalIds given but empty; a segment that isn't this website's; an unknown topic. |
400 | segment_needs_a_rule, name_required, nothing_to_update | Segments — see Segments — and topics or templates without a name. |
400 | event_required, action_title_required | An event or automation without an event name; an automation without action.title. |
400 | trigger_event_required, at_least_one_step_required | Sequences (journeys). |
400 | content_title_required, invalid_timezone, invalid_url, invalid_segment, name_and_event_required | Recurring schedules and website settings, webhooks and feeds, goals. |
400 | invalid_settings, invalid_prompt, invalid_welcome, invalid_color, invalid_retention_days | Settings, prompt and welcome bodies. |
400 | invalid_required, invalid_scopes, invalid_expiry | PUT /identity; creating an API key. |
400 | invalid_range, invalid_tz, invalid_status, invalid_cursor | Report and list filters. |
400 | ids_required, too_many_ids, invalid_id, invalid_label, reserved_label, too_many_labels, nothing_to_change, invalid_filters, invalid_device | The /api/v1/audience routes. |
400 | invalid_file, no_subscribers, no_account_email | An upload that isn't an image; an empty import; POST /reports/test without an account email. |
400 | unknown_field | A send field written in snake_case (external_id, segment_id, send_at …). It would be ignored and the send would reach everyone, so it is refused; use names the camelCase field. |
401 | not_authenticated, invalid_api_key, api_key_expired | No key sent (and no dashboard session); a key that isn't recognised or was revoked; a key past its expiry date. |
403 | insufficient_scope | The key lacks the scope named in required (see scopes). |
403 | email_not_verified | Sends are refused until the account's email address is verified. |
403 | account_suspended | The account is suspended. |
403 | session_required, identity_off_requires_dashboard | Only the signed-in owner can do this: create keys, revoke another key, rotate the identity secret, turn identity verification off. |
404 | not_found | The id isn't one of this website's — including any id that isn't a valid UUID. |
404 | not_scheduled, project_not_found, subscriber_not_found | Cancelling a send that isn't waiting; a public SDK route with an unknown Project ID; topic preferences for a browser that isn't subscribed. |
409 | not_a_draft, too_many_drafts | Only a draft can be changed, sent or deleted that way; a website keeps up to 500 drafts. |
409 | segment_in_use | Deleting a segment that feeds, schedules or pending sends still use. |
409 | structural_change_requires_no_active_enrollments | Reordering or removing journey steps while people are in the journey. |
409 | name_taken | Renaming a goal to the name of another goal. |
409 | delivery_in_progress, body_too_large | Resending a webhook delivery that already has an attempt waiting, or whose event was over 64 KB. |
413 | draft_too_large, payload_too_large, file_too_large | A draft holds up to 256 KB, a request body up to 1 MB, an uploaded image up to 2 MB. |
429 | rate_limited, send_limit_reached | Too many requests, or the website's hourly send budget is used up — see the limits below. |
500 | internal_error | Something went wrong on our side; try again later. |
Every /api/v1 route checks the credential first, so an unknown path answers 401 not_authenticated without a key (and a plain 404 page with one). Request bodies can be up to 1 MB.
Rate limits
| Requests | Limit | Counted per |
|---|---|---|
POST /api/v1/notifications and /notifications/:id/send (every request; saving a draft isn't counted) | 60 a minute and 600 an hour | API key (dashboard: per account) |
| Broadcasts: sends to everyone, a segment, a topic or filters | 30 per clock hour | website |
Sends to specific users (externalId / externalIds), or transactional: true with a segment or filters | 1,000 per clock hour | website |
| Test sends to your own devices (dashboard) | 120 per clock hour | website |
POST / GET /api/v1/events with an API key | 3,000 a minute | API key |
Browser SDK requests (/api/v1/projects/…: config, subscribe, track(), inbox, preferences; click and delivery pings), and /api/v1/events without a key | 120 a minute | visitor IP address |
| Sign-up, sign-in (including two-factor and Google/GitHub sign-in), email verification, password reset; changing the account's password or email; turning two-factor on or off; deleting or restoring the account | 30 per 15 minutes | IP address |
Other dashboard account routes (/api/v1/auth/me, /api/v1/account, …) | 120 a minute | IP address |
POST /api/v1/ai/suggest | 30 an hour | IP address |
POST /api/v1/upload | 60 an hour | IP address |
POST /api/v1/support | 5 an hour | account |
Other API-key routes (stats, reports, segments, automations, settings, …) have no request limit of their own; keep to a sensible pace.
Going over a request limit answers 429 rate_limited; going over one of the three hourly send budgets (per website) answers 429 send_limit_reached. A limited route's responses carry RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until the window resets). Every 429 also carries Retry-After (seconds): wait that long before trying again, rather than retrying in a loop. The hourly send budgets restart at the top of each hour; a send_limit_reached answer says which budget ran out: { "error": "send_limit_reached", "message": "…", "limit": 30, "kind": "broadcast", "retryAfter": 1260 } (kind is broadcast, transactional or test). A send refused this way isn't counted.
Sending rules: quiet hours and a per-subscriber daily frequency cap are set per website (Settings → Sending) and apply automatically — no per-request flags needed. Quiet hours hold a send that would go out now until the quiet time ends; the cap skips subscribers who already got that many notifications in the last 24 hours. Sends with transactional: true skip both.