#Errors and pagination
#Error envelope
Every non-2xx response uses one shape, and the HTTP status always matches the code:
{ "error": { "code": "not_found", "message": "Message not found." } }Branch on error.code, never on message. Validation failures list the offending fields in error.details:
{
"error": {
"code": "validation_error",
"message": "The request failed validation.",
"details": [{ "path": ["to"], "message": "Invalid phone number" }]
}
}| Code | HTTP | Meaning |
|---|---|---|
validation_error | 400 | The request failed validation. details lists the fields. |
unauthorized | 401 | Missing, malformed, revoked or unknown credentials. |
forbidden | 403 | The credential is valid but may not do this. |
not_found | 404 | The resource does not exist, or belongs to another account. |
conflict | 409 | The request conflicts with the current state of the resource. |
rate_limited | 429 | Too many requests. Slow down and retry. |
budget_exceeded | 402 | The account monthly spend limit has been reached. |
payment_required | 402 | The prepaid wallet balance is empty. Top it up and retry. |
channel_unsupported | 422 | The destination or sender cannot be used on this channel. |
address_suggestion | 422 | The address can only be placed after a correction. details.suggested carries it; resubmit. |
provider_error | 502 | An upstream carrier or payment provider failed. Safe to retry later. |
internal_error | 500 | Something went wrong on our end. |
In the SDK, every non-2xx response throws MittisError with status and the API's safe message.
Retry 429, 502 and 500 with backoff. Do not blindly retry 400, 401, 403, 404, 409 or 422: the request itself needs to change.
#Pagination
List endpoints return a page envelope, newest first:
{ "data": [], "hasMore": false, "nextCursor": null }Pass nextCursor back as ?cursor= (with limit) to fetch the next, older page. Cursors are opaque: store and replay them, do not parse or build them. Small bounded lists are returned whole with hasMore: false. Where an endpoint notes it, ?format=array (or Accept: application/vnd.mittis.list+json) returns a bare array instead of the envelope.
curl "https://api.mittis.ai/v1/messages?limit=50" \
-H "Authorization: Bearer $MITTIS_API_KEY"
# next page
curl "https://api.mittis.ai/v1/messages?limit=50&cursor=$NEXT_CURSOR" \
-H "Authorization: Bearer $MITTIS_API_KEY"let cursor: string | null = null;
do {
const url = new URL('https://api.mittis.ai/v1/messages');
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('cursor', cursor);
const page = await (
await fetch(url, { headers: { authorization: `Bearer ${process.env.MITTIS_API_KEY}` } })
).json();
for (const message of page.data) handle(message);
cursor = page.hasMore ? page.nextCursor : null;
} while (cursor);The events feed (GET /v1/events) pages differently, forward in time with after. See Webhooks.
#Idempotency
POST /v1/messages, POST /v1/emails and POST /v1/ports accept an Idempotency-Key header. A retry with the same key returns the original result, marked Idempotent-Replayed: true, instead of repeating the action. Use a fresh key per logical send.
#Money
All amounts are integer cents unless a field name says otherwise.