Skip to content

Error reference

Last updated 7 September 2026

Every error body carries a docs link back to this page, anchored to the exact code. If you followed one here, the row you want is highlighted.

The envelope

Every failure returns the same three fields, at every status. error is stable and safe to branch on; message is written for a human reading a log and may change.

Some errors add fields — a rejected batch item carries its index, a throttled account carries the rate that caused it. Treat anything beyond the three below as advisory.

JSON
{
  "error": "recipient_not_allowlisted",
  "message": "Key allowlist is [@example.com]. Recipient is outside it.",
  "docs": "https://www.sadasend.com/docs/errors#recipient_not_allowlisted"
}

Authentication and scope

CodeStatusMeans
missing_api_key401No Authorization header. Send Authorization: Bearer sada_…
invalid_api_key401The key does not exist, or was revoked. Keys are shown once at creation and cannot be recovered
unauthenticated401A dashboard session was required and none was present. API keys do not authenticate these routes
insufficient_scope403The key is valid but its scope set does not include this operation. Scopes are fixed at creation — mint a new key rather than widening one
api_key_expired401The API key has passed its expiration date (expires_at). Mint a new key or update the expiration in the dashboard
ip_not_allowed403The client IP address is not permitted by this key’s CIDR IP allowlist (ip_allowlist)
forbidden_origin403Origin header from an untrusted browser domain was rejected on API or MCP connection
key_revoked409The key that originally sent this message has since been revoked, so it cannot be used to resend it

Sending

The codes worth handling explicitly. A send that fails with any of these has not been queued and will not be retried for you.

CodeStatusMeans
missing_from400No from address
missing_to400No recipient
invalid_from400The from address is not a valid address, or its domain is not one of yours
invalid_source400Header X-SadaSend-Source must be one of: agent, backend, automation, system
invalid_agent_name400Header X-SadaSend-Agent must not exceed 128 characters
domain_not_found403The sending domain is not on this account
domain_not_verified403The domain exists but SPF, DKIM or DMARC has not verified. Nothing sends from an unverified domain
recipient_not_allowlisted403The key carries a recipient allowlist and this recipient is outside it. This is the guardrail working
sandbox_limit_reached403The shared onboarding domain has a small lifetime send cap per account, so one signup cannot spend the shared reputation. Verify your own domain to lift it
recipient_suppressed422The address is on your suppression list — a previous hard bounce or complaint. Removing it is a dashboard action, deliberately
attachment_too_large422A single attachment exceeds the 10MB individual file size limit
attachments_too_large422Combined attachments on a single message exceed the 25MB total limit
invalid_attachment422Attachment is missing required filename or content, or base64 data is malformed
quota_exceeded402Account has used all monthly email credits on its current plan. Upgrade to resume sending
account_suspended403Account is suspended due to excessive complaint rate or policy restriction. Contact support
payload_too_large413The request payload exceeds the maximum allowed size (25MB for mail, 1MB for API bodies)
account_throttled429The account is over its complaint or bounce threshold and is rate-limited, not stopped. Transactional mail continues at a reduced rate
rate_limit_exceeded429Rate limit exceeded for this API key or client IP address
daily_limit_reached429The plan’s daily recipient limit. Only the free plan has one. retryAfterSeconds says when, and it is a rolling day rather than a calendar one
idempotency_key_reused409This Idempotency-Key was used for a different request body. Reuse it only to retry the identical send
idempotency_key_in_progress409The first request with this key has not finished. Retry shortly — do not send a new key
attachment_storage_failed503The attachment could not be stored. The send was not accepted; retry is safe

Batches, logs and message actions

CodeStatusMeans
empty_batch400The batch contained no messages
batch_too_large400Over the per-call limit. Split the batch
missing_emails400The batch body had no emails array
invalid_status400Unknown status in a log filter
invalid_date400A date filter could not be parsed
invalid_cursor400The pagination cursor is malformed or from a different query. Start the listing again
message_not_found404No message with that id on this account
not_cancellable409The send has already left the queue. Only a scheduled send can be cancelled
body_unavailable409The rendered body has aged past your plan retention, so it cannot be read or resent
no_originating_key409The message has no surviving key to resend it with

Domains, keys and approvals

CodeStatusMeans
invalid_domain400Not a valid domain name
domain_exists409That domain is already on the account
domain_quota_exceeded403The plan sending domain limit has been reached. Upgrade to add more domains
history_not_found404Deleted domain record was not found in audit history for 1-click restore
restore_failed500Failed to restore deleted domain and its preserved DKIM records
invalid_scope400Unknown scope name in a key request
invalid_mode400Unknown key mode. Modes are live, approval and test — an approval-mode key holds every send for a human decision
invalid_rate_limit_max400rate_limit_max must be an integer between 1 and 1,000,000
invalid_rate_limit_window400rate_limit_window must be one of: 1s, 1m, 1h, 1d
invalid_expires_at400Expiration date must be a valid future ISO 8601 timestamp
invalid_ip_allowlist400IP allowlist must contain valid IPv4/IPv6 addresses or CIDR subnets
invalid_default_source400Default source must be one of: agent, backend, automation, system
invalid_allowlist400Recipient allowlist cannot be an empty array. Provide addresses or omit for all
key_not_found404No key with that id on this account
invalid_decision400An approval decision must be an approval or a rejection
already_decided409That held send has already been approved or rejected. Approvals are not reversible

Webhooks

CodeStatusMeans
missing_url400No endpoint URL
invalid_url400The URL could not be parsed
insecure_url400Endpoints must be https. Signed events are not sent in the clear
no_events400Subscribe to at least one event
unknown_event400Unrecognised event name
invalid_id400The webhook id is not a valid id
invalid_json400The request body was not valid JSON
missing_target400Provide either webhookId or url to simulate delivery
unsupported_protocol400Simulation target URL must use http or https (e.g. http://localhost:PORT/webhook)
invalid_range400A replay window that is malformed or longer than the maximum
webhook_not_found404No endpoint with that id on this account
webhook_disabled409The endpoint is disabled — repeated failures disable one automatically. Re-enable it before replaying

Templates and suppressions

CodeStatusMeans
missing_name400A template needs a name
name_too_long400Template or API key name exceeds 100 characters
missing_subject400A template needs a subject
missing_content400A template needs html or text
template_not_found404No template with that id on this account
template_too_large413The template body is over the size limit
template_render_failed422The template could not render with the variables given — usually a missing variable. The message names it
invalid_address400Not a valid email address
invalid_tag400A tag is too long, or uses characters outside letters, digits and . _ : -
too_many_tags400A message may carry ten tags
unsupported_engine400Templates render with Liquid. React Email renders in your own build — see /docs/react-email
suppression_not_found404That address is not suppressed

Account, team and support

These come from the dashboard-facing routes rather than from an integration, but they use the same envelope.

CodeStatusMeans
invalid_body400The request body was not an object
nothing_to_update400A PATCH with no recognised fields
invalid_name400Name missing or too long
missing_org400Organisation name required
org_too_long400Organisation name over the limit
invalid_country400Not a recognised country code
invalid_phone400Phone number could not be parsed
invalid_website400Website is not a valid URL
invalid_industry400Not a recognised industry value
invalid_referral400Not a recognised referral source
invalid_account_type400Not a recognised account type
invalid_email400Not a valid email address
disposable_email400The address is a known disposable-mailbox provider
owner_only403Only the account owner may do this
account_not_found404No such account
seat_quota_reached402The plan seat limit is reached. Remove a member or move up a plan
webhook_quota_reached402The plan’s endpoint limit. Delete an endpoint or move up a plan
template_quota_reached402The plan’s template limit, counted in names — saving a new version of an existing template never counts against it
request_limit_reached429The plan’s daily API request allowance, counted per account rather than per key. Carries Retry-After
already_a_member409That address is already on the account
invitation_not_found404The invitation does not exist or has been withdrawn
missing_message400A ticket needs a message
message_too_long400The ticket message is over the limit
ticket_not_found404No ticket with that id on this account

Free tools and sign-in

The tool checks are unauthenticated and rate-limited by source. The sign-in codes surface in the browser rather than in an integration.

CodeStatusMeans
unknown_check404Not one of the supported checks
invalid_selector400The DKIM selector is malformed
too_many_checks429Rate limit for unauthenticated checks. Wait and retry
missing_token400The sign-in link had no token
no_identity400The identity provider returned no usable identity
state_invalid400The sign-in state did not match. Start again
unsupported_provider400Unknown identity provider
oauth_not_configured501That provider is not configured on this deployment