Error codes
Every failed request carries a stable error.code. Match on that, never on error.message — messages are written for people and change.
json
{
"success": false,
"status_code": 422,
"error": {
"code": "notifyzr.send.error.sender_domain_not_verified",
"message": "Sender domain 'example.com' is not verified for this organization",
"issues": []
},
"payload": null
}Codes are namespaced notifyzr.<domain>.error.<name>. A code is never reused for a different meaning, and a new one may appear in any release — treat an unrecognised code as the generic failure its status code implies.
Common
| Code | Status | Meaning |
|---|---|---|
notifyzr.common.error.unknown | 500 | Unhandled failure. |
notifyzr.common.error.service_unavailable | 503 | A dependency (broker, database) is down. Retry. |
notifyzr.common.error.rate_limited | 429 | See Rate limiting. |
notifyzr.common.error.validation | 422 | Schema validation failed. Per-field detail is in error.issues. |
notifyzr.common.error.http.not_found | 404 | No such route, or no such record in your organization. |
notifyzr.common.error.http.method_not_allowed | 405 | Wrong verb for that route. |
Authentication
| Code | Status | Meaning |
|---|---|---|
notifyzr.common.error.auth.no_header | 401 | No X-API-Key header. |
notifyzr.common.error.auth.invalid_header | 401 | The header is present but unusable. |
notifyzr.common.error.auth.invalid_credentials | 401 | Credentials rejected. |
notifyzr.common.error.auth.forbidden | 403 | Authenticated, not permitted. |
notifyzr.common.error.apikey.invalid | 401 | Unknown key. |
notifyzr.common.error.apikey.malformed | 401 | The key is not shaped like a key. |
notifyzr.common.error.apikey.unauthorized | 401 | Invalid or inactive key. |
notifyzr.common.error.apikey.expired | 401 | Past its expires_at. |
Expired, revoked and invalid are the same answer
All three are 401 with no hint about which. Anything else would let a caller probe which keys have existed.
Sending
| Code | Status | Meaning |
|---|---|---|
notifyzr.send.error.no_recipients | 422 | No recipients — including the case where every recipient was on the suppression list. |
notifyzr.send.error.no_body | 422 | Neither a body nor a template_id. |
notifyzr.send.error.sender_required | 422 | No sender address on a channel. There is no implicit from-address. |
notifyzr.send.error.sender_domain_not_verified | 422 | The sender's domain is not a verified domain of this organization. |
notifyzr.messenger.error.send_failed | 500 | The send could not be accepted. |
notifyzr.messenger.error.channel_unsupported | 422 | Unknown channel type. |
notifyzr.messenger.error.channel_not_implemented | 501 | Known type, no sender for it. |
notifyzr.message.error.not_found | 404 | No such message. |
All four send errors are raised before the message is recorded and before quota is spent, so a refused send costs nothing.
Contacts
| Code | Status | Meaning |
|---|---|---|
notifyzr.contact.error.not_found | 404 | |
notifyzr.contact.error.email_exists | 422 | Addresses are unique per organization. |
notifyzr.contact.error.property_invalid_value | 422 | A property value does not match its declared type. |
notifyzr.contact_property_definition.error.not_found | 404 | |
notifyzr.contact_property_definition.error.key_exists | 422 | |
notifyzr.contact_property_definition.error.limit_reached | 422 | |
notifyzr.contact_import.error.not_found | 404 | |
notifyzr.contact_import.error.unsupported_media_type | 415 | Send multipart/form-data or JSON with a csv string. |
notifyzr.unsubscribe.error.not_found | 404 |
Segments and broadcasts
| Code | Status | Meaning |
|---|---|---|
notifyzr.segment.error.not_found | 404 | |
notifyzr.segment.error.slug_exists | 422 | |
notifyzr.segment.error.invalid_filter | 422 | The filter tree broke a rule. The message names which. |
notifyzr.segment.error.limit_reached | 422 | |
notifyzr.segment.error.unknown_contacts | 422 | A contact id given to add as a member is not in your organization. Nothing was added. |
notifyzr.segment.error.member_not_found | 404 | The contact was not added to the segment by hand. |
notifyzr.broadcast.error.not_found | 404 | |
notifyzr.broadcast.error.not_editable | 422 | Only a draft can be edited. |
notifyzr.broadcast.error.not_deletable | 422 | Only a draft can be deleted. |
notifyzr.broadcast.error.already_sent | 422 | It has left the draft state. |
Templates
| Code | Status | Meaning |
|---|---|---|
notifyzr.template.error.not_found | 404 | |
notifyzr.template.error.not_published | 422 | A draft cannot be used to send. |
notifyzr.template.error.body_required | 422 | Publishing needs at least one body. |
notifyzr.template_folder.error.not_found | 404 | |
notifyzr.template_folder.error.depth_exceeded | 422 | Folders nest three levels. |
notifyzr.template_folder.error.cycle | 422 | A folder cannot be moved inside itself. |
notifyzr.template_folder.error.limit_reached | 422 |
Automations
| Code | Status | Meaning |
|---|---|---|
notifyzr.automation.error.not_found | 404 | |
notifyzr.automation.error.invalid_trigger | 422 | Not an event in the catalog. |
notifyzr.automation.error.invalid_conditions | 422 | |
notifyzr.automation.error.invalid_actions | 422 | |
notifyzr.automation.error.incomplete_graph | 422 | The graph does not satisfy the rules a published automation must. A draft is not held to them. |
Forms
| Code | Status | Meaning |
|---|---|---|
notifyzr.form.error.not_found | 404 | Also returned for an inactive form's submission endpoint. |
notifyzr.form.error.no_fields | 422 | |
notifyzr.form.error.invalid_fields | 422 | |
notifyzr.form.error.unsupported_media_type | 415 | The submission endpoint takes form encodings only. |
Domains, channels and webhooks
| Code | Status | Meaning |
|---|---|---|
notifyzr.domain.error.not_found | 404 | |
notifyzr.domain.error.invalid | 422 | Not a usable domain name. |
notifyzr.domain.error.exists | 422 | Already added to this organization. |
notifyzr.channel.error.not_found | 404 | |
notifyzr.channel.error.invalid | 422 | Missing or malformed credentials for that channel type. |
notifyzr.webhook.error.not_found | 404 | |
notifyzr.webhook.error.invalid_url | 422 | Must be an absolute http(s) URL. |
Quota
| Code | Status | Meaning |
|---|---|---|
notifyzr.quota.error.exceeded | 422 | A plan ceiling was reached. GET /v1/usage says which. |