Sending
Errors
A stable code to branch on, a message to read, and details when the failure has structure worth inspecting.
The envelope{
"error": {
"code": "invalid_request",
"message": "Email request validation failed",
"details": { "issues": [ { "path": ["to"], "message": "Invalid input" } ] }
}
}Branch on code, not on message — messages get clearer over time, codes do not change.
Statuses
| Field | Type | Notes |
|---|---|---|
400 | Bad request | The Idempotency-Key header is missing or too long. |
401 | Unauthorized | The key is missing, malformed, expired or revoked. Indistinguishable on purpose. |
403 | Forbidden | The key is good but lacks the scope; the message names the scope it wanted. |
404 | Not found | No such record in your organization. A record in someone else's reads the same way. |
409 | Conflict | The idempotency key was already used with a different request. |
422 | Unprocessable | The request was understood and refused. `details.issues` says which fields. |
429 | Too many requests | Slow down. Retry-After says how long. |
5xx | Ours | Retry with the same idempotency key; it cannot double-send. |
What is safe to retry
A 429 or a 5xx is worth retrying with backoff. A 4xx that is not 429 will fail the same way until you change the request. Retry a send with the same idempotency key and it cannot charge you twice — that is what the key is for.
Not found, or not yours
Every record is scoped to the organization the key belongs to. Asking for one that belongs to someone else returns 404, the same as one that does not exist, because the alternative tells you it exists.