Errors
The single most important thing about this API’s status codes:
An address that fails verification is a successful request.
undeliverablearrives as a200with a negativestatus. The 4xx and 5xx responses below mean the request was malformed or the service could not answer.
An integration that treats any non-2xx as “bad address” will reject people during our outages and accept nothing during a rate limit. Treat them as what they are: your bug, your balance, or our problem.
{ "error": "insufficient_credits", "message": "the account has nothing left to spend" }error is a stable machine-readable string. message is prose and may be
reworded; do not branch on it.
POST /v1/verifications
Section titled “POST /v1/verifications”| Status | Meaning | What to do |
|---|---|---|
400 |
Malformed body, missing email, an unrecognised variant, or an Idempotency-Key that is not one valid key (invalid_idempotency_key). |
Fix the request. It will not succeed on retry. |
401 |
Missing or unrecognised credential. | See Authentication. |
402 |
insufficient_credits - the account has nothing left to spend. |
Top up. Deliberately not reported as unknown: what somebody has paid for is not something this service is uncertain about. |
403 |
delegation_not_permitted - the request named an account to charge and this credential may not name one. |
Remove the header. Ordinary credentials bill their own account. |
409 |
idempotency_conflict - this Idempotency-Key was already used for a different address or depth. |
Use a new key for a new question. See Idempotency. |
413 |
The request body is larger than permitted. | Fix the request. |
429 |
Caller or source budget exceeded, or the request pattern looks like directory harvesting. | Back off; honour Retry-After. See Limits. |
500 |
verification_failed - the verification engine failed, and nothing was charged. This says nothing about the address. |
Retry, then proceed behind a confirmation message. |
503 |
accounting_unavailable - the balance cannot be reached, so no work was done; accounting_paused - accounting is paused, honour Retry-After; or another dependency needed to authorise the request is unavailable. |
Retry with backoff. Nothing was charged. |
503 |
accounting_uncertain - the charge could not be confirmed. |
Retry with the same key, or read the operation named in X-Credits-Operation-ID. See Credits. |
GET /v1/verifications/{id}
Section titled “GET /v1/verifications/{id}”| Status | Meaning |
|---|---|
400 |
No identifier supplied. |
401 |
Missing or unrecognised credential. |
404 |
No such verification for this credential. An identifier belonging to another caller answers identically to one that never existed. |
500 |
The verification could not be retrieved. |
503 |
Stored results are unavailable. |
POST /v1/disposable
Section titled “POST /v1/disposable”| Status | Meaning |
|---|---|
400 |
missing_domain, or invalid_domain for something no lookup can be made for. |
401 |
Missing or unrecognised credential. |
413 |
The request body is larger than permitted. |
429 |
Caller or source budget exceeded. |
503 |
annotation_unavailable - the reference lists are not loaded. Treat it as no answer, never as a negative one. See The disposable lookup. |
There is no 402 here, and its absence is not an omission: the operation spends
no credits, so an empty balance cannot refuse it. The same is true of
POST /v1/accept-all and
POST /v1/public-provider, whose pages list their
errors.
GET /v1/domain/{domain}
Section titled “GET /v1/domain/{domain}”The domain lookup has its own table,
because its key is mandatory and it has a way of failing the others do not: a
charge that could not be confirmed (503 commit_unknown), answered by retrying
under the same key.
GET /v1/credit-operations/{id}
Section titled “GET /v1/credit-operations/{id}”| Status | Meaning |
|---|---|
401 |
Missing or unrecognised credential. |
404 |
No such operation for this credential and account. |
503 |
accounting_unavailable - the balance cannot be read right now. |
What to do when we cannot answer
Section titled “What to do when we cannot answer”Proceed behind a confirmation message.
This is worth stating flatly because the tempting alternative - fail the signup
- converts our unavailability into your lost customers, silently. The service
itself is built on the same rule: when a dependency is down it degrades to
unknownand keeps answering rather than failing, andunknownnever blocks a registration. The one exception is the balance - a paid request is refused with a503rather than served unpaid - and from your side that503is one more reason to proceed behind confirmation, not to refuse the signup. Your side of the integration should hold the same line.
A reasonable default:
let result;try { result = await verify(email, key);} catch { // Unreachable, timed out, 5xx - all the same thing from here. result = { status: 'unknown', action: 'allow_with_email_confirmation' };}Timeouts are yours to set
Section titled “Timeouts are yours to set”Set a client timeout and treat expiry as unknown. The service will answer
pending rather than hold a socket open indefinitely, so a long hang is a
network problem rather than a slow verification - and a signup form should not
wait on one.