Guides
Errors
Every error has the same shape: a stable code for your program and a sentence for the person reading the log.
The shape
A request that did not succeed is answered with a 4xx or 5xx status and a JSON body. Branch on error, which does not change. Show or log message, which is written for a person and may be reworded.
400 Bad Request
{
"error": "bad_request",
"message": "The body is not valid JSON."
}Status codes
| Status | error | Means | Retry |
|---|---|---|---|
| 400 | bad_request | The request is not valid: the body is not JSON, a field is missing or wrong. The message says which and why. | Fix, do not retry |
| 401 | unauthorized | No key, a key in the wrong shape, or a key that is not valid any more. | Fix, do not retry |
| 402 | payment_required | The account’s plan does not include this, or a plan limit applies. The message says what is needed. | Fix, do not retry |
| 403 | forbidden | The key does not hold the scope this operation needs; the message names it. Also a role that may not do this. | Fix, do not retry |
| 404 | not_found | There is no such endpoint, or no such object in this account. | Fix, do not retry |
| 409 | conflict | The request conflicts with the current state, such as a domain that is not set up for sending yet. | Fix, do not retry |
| 409 | refused | A guard said no: a recipient who bounced, unsubscribed or complained, or sending stopped for the account. Retrying cannot help. | Fix, do not retry |
| 413 | too_large | The request body is over 2 MB. | Fix, do not retry |
| 429 | rate_limited | Too many requests for this key in a short time. The retry-after header says how many seconds to wait. | After retry-after |
| 429 | too_many | An account limit that frees up with time, such as the hourly transactional allowance. The message says which. | After retry-after |
| 500 | internal | Something went wrong on our side. | With backoff |
| 502 | upstream | Something went wrong on our side, or with a provider behind us. | With backoff |
| 502 | ambiguous | A provider may or may not have taken a message. It is recorded as not sent; check the log before sending again. | Check first |
| 503 | not_configured | The API is not switched on for this deployment. | With backoff |
A refusal is not a failure
The difference matters. A failure (500, 502, 503) means we tried and something broke, so a retry later may work. A refusal (refused, and every other 4xx except 429) means a rule said no, and sending the same request again will be refused again. Fix the request, or tell the person.
Retries that cannot send twice
Give each message an
idempotencyKey when you send email. A retry with the same key returns the first result, so a timeout on your side can be retried safely.Handling errors
Node
const res = await fetch(url, init);
if (!res.ok) {
const { error, message } = await res.json();
if (res.status === 429) {
const wait = Number(res.headers.get('retry-after') ?? 1);
// wait that many seconds, then try again
}
throw new Error(`Rykon Mail ${res.status} ${error}: ${message}`);
}A response may carry an x-request-id header. Include it when you write to us about a request; it finds the request in our logs.