Skip to content

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.

StatusCodeWhen
400invalid_jsonThe body isn't valid JSON (send Content-Type: application/json).
400title_required, external_ids_empty, segment_not_found, unknown_topicA send (or a draft being sent): no title; externalId / externalIds given but empty; a segment that isn't this website's; an unknown topic.
400segment_needs_a_rule, name_required, nothing_to_updateSegments — see Segments — and topics or templates without a name.
400event_required, action_title_requiredAn event or automation without an event name; an automation without action.title.
400trigger_event_required, at_least_one_step_requiredSequences (journeys).
400content_title_required, invalid_timezone, invalid_url, invalid_segment, name_and_event_requiredRecurring schedules and website settings, webhooks and feeds, goals.
400invalid_settings, invalid_prompt, invalid_welcome, invalid_color, invalid_retention_daysSettings, prompt and welcome bodies.
400invalid_required, invalid_scopes, invalid_expiryPUT /identity; creating an API key.
400invalid_range, invalid_tz, invalid_status, invalid_cursorReport and list filters.
400ids_required, too_many_ids, invalid_id, invalid_label, reserved_label, too_many_labels, nothing_to_change, invalid_filters, invalid_deviceThe /api/v1/audience routes.
400invalid_file, no_subscribers, no_account_emailAn upload that isn't an image; an empty import; POST /reports/test without an account email.
400unknown_fieldA 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.
401not_authenticated, invalid_api_key, api_key_expiredNo key sent (and no dashboard session); a key that isn't recognised or was revoked; a key past its expiry date.
403insufficient_scopeThe key lacks the scope named in required (see scopes).
403email_not_verifiedSends are refused until the account's email address is verified.
403account_suspendedThe account is suspended.
403session_required, identity_off_requires_dashboardOnly the signed-in owner can do this: create keys, revoke another key, rotate the identity secret, turn identity verification off.
404not_foundThe id isn't one of this website's — including any id that isn't a valid UUID.
404not_scheduled, project_not_found, subscriber_not_foundCancelling a send that isn't waiting; a public SDK route with an unknown Project ID; topic preferences for a browser that isn't subscribed.
409not_a_draft, too_many_draftsOnly a draft can be changed, sent or deleted that way; a website keeps up to 500 drafts.
409segment_in_useDeleting a segment that feeds, schedules or pending sends still use.
409structural_change_requires_no_active_enrollmentsReordering or removing journey steps while people are in the journey.
409name_takenRenaming a goal to the name of another goal.
409delivery_in_progress, body_too_largeResending a webhook delivery that already has an attempt waiting, or whose event was over 64 KB.
413draft_too_large, payload_too_large, file_too_largeA draft holds up to 256 KB, a request body up to 1 MB, an uploaded image up to 2 MB.
429rate_limited, send_limit_reachedToo many requests, or the website's hourly send budget is used up — see the limits below.
500internal_errorSomething 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

RequestsLimitCounted per
POST /api/v1/notifications and /notifications/:id/send (every request; saving a draft isn't counted)60 a minute and 600 an hourAPI key (dashboard: per account)
Broadcasts: sends to everyone, a segment, a topic or filters30 per clock hourwebsite
Sends to specific users (externalId / externalIds), or transactional: true with a segment or filters1,000 per clock hourwebsite
Test sends to your own devices (dashboard)120 per clock hourwebsite
POST / GET /api/v1/events with an API key3,000 a minuteAPI key
Browser SDK requests (/api/v1/projects/…: config, subscribe, track(), inbox, preferences; click and delivery pings), and /api/v1/events without a key120 a minutevisitor 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 account30 per 15 minutesIP address
Other dashboard account routes (/api/v1/auth/me, /api/v1/account, …)120 a minuteIP address
POST /api/v1/ai/suggest30 an hourIP address
POST /api/v1/upload60 an hourIP address
POST /api/v1/support5 an houraccount

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.