Conventions
Response envelope
Every JSON response uses the same shape:
{
"success": true,
"message": "Contacts retrieved",
"status_code": 200,
"error": null,
"payload": { }
}On failure, error is populated instead:
{
"success": false,
"message": "The template is not published",
"status_code": 422,
"error": {
"code": "notifyzr.template.error.not_published",
"message": "The template is not published",
"issues": []
},
"payload": null
}Match on error.code. Codes are namespaced notifyzr.<domain>.error.<name> and are stable; the human-readable message is not.
issues carries per-field validation detail when a request fails schema validation.
Status codes
| Code | Meaning |
|---|---|
200 / 201 | Success |
202 | Accepted — queued work, e.g. a broadcast fan-out or a contact import |
400 | Malformed request |
401 | Missing, invalid, inactive or expired API key |
403 | Authenticated, but not permitted |
404 | Not found in your organization — which is also the answer when it exists in another one |
422 | Well-formed but rejected by a business rule |
429 | Rate limited |
Why 404 and not 403
Cross-organization access returns 404, never 403. A 403 would confirm that the id exists somewhere, which is information about another organization.
Pagination
List endpoints take page and per_page (default 25, maximum 100) and return totals alongside the rows.
curl "http://api.localhost/v1/contacts?page=2&per_page=100" -H "X-API-Key: $KEY"Rate limiting
Requests are rate-limited per API key. Exceeding the limit returns 429. Broadcast fan-out and automation actions run inside the platform and are governed by quotas rather than this limit.
Quotas
Plans carry monthly ceilings (messages, contacts, automation runs). Crossing one does not corrupt work in flight: an import or a fan-out that runs out of quota completes with the remainder counted as skipped. Owners and admins are emailed at 80% and again at the limit.
Check current usage at GET /v1/usage.