Quickstart
Base URL https://api.mailotp.org. Everything is JSON over HTTPS and there is no SDK to install.
Three calls take you from an empty account to a delivered code.
mo_live_; Show on that page brings it back later.POST /v1/tasks with a service slug — or any site's domain — returns a real mailbox address.GET /v1/tasks/{id}?wait=30 blocks until the code lands, then returns it.$ curl -X POST https://api.mailotp.org/v1/tasks \
-H "Authorization: Bearer mo_live_…" \
-H "Idempotency-Key: order-8812" \
-d '{"service":"instagram","domain":"hotmail.com"}'
Authentication
Send the key as a Bearer token, or as X-API-Key if that fits your client better — both are accepted
and behave identically. Keys are 40 characters long; press Show on the keys page to see one again.
Authorization: Bearer mo_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# or
X-API-Key: mo_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
A key carries read and tasks:write. Treat it like a password: anyone holding it can spend
your balance, so keep it server-side. Revoking takes effect within 30 seconds. The full machine-readable contract is at
https://api.mailotp.org/v1/openapi.json.
Task lifecycle
A task rents one mailbox for one service. The service price is held immediately. When a matching mail lands
(inbox or junk) the code is extracted and the hold is captured. If the window passes with no code, or you cancel after
cancellable_at, the hold is refunded.
Tasks that never get a code are limited. If fewer than 10% of your last 500
finished tasks for a service received a code, you can hold at most 10 waiting tasks for that service; one
more gets 429 low_success_limit with Retry-After. Honour it: unlike other 429s these count as
failed requests, so retrying more than 500 times a minute triggers the failure pause. The limit lifts by
itself as codes arrive again.
Only mail that arrives after the task is created counts. Create the task (or call /again) first,
then have the site send the code. Mail that was already in the mailbox — even from a few seconds earlier, or the same
second — is ignored, so an older code is never handed to a new task.
A second code on the same address is free for 10 minutes. Platforms often send a second email
for the same signup. Call /again within 10 minutes of the last charged code on that address and the new task
costs nothing ("price": "0", no hold). A free code does not extend that time; after it, /again
is charged like any task. Not for the other catch-all service.
How long is the window? Every service sets its own, returned as ttl_sec by
GET /v1/services — today that is 5 minutes for every service. Send
ttl on POST /v1/tasks to ask for a different one; values are clamped to
60–900 seconds. The task times out the moment the window closes and the hold is released automatically.
Create a task
POST /v1/tasks with the service slug. Optional domain (e.g. hotmail.com) and an
Idempotency-Key header so retries never double-charge.
Site not in the catalog? Send the site's domain as service ("service": "nusuk.sa"). If
the domain belongs to a catalog service you get that service. Otherwise the task opens under other, bound to
that site: "site": "nusuk.sa" in the response, an address that site has never seen, and mail from another
catalog service is not delivered to it. Codes, links or both arrive as usual. You don't have to wait for a site to
appear in GET /v1/services before ordering it — order it by domain any time. Once a code has been
delivered for a site, it joins the catalog automatically (within a few minutes) as its own service with its own slug.
Still using service: "other"? It keeps working as before: the first code from any sender. Add
site and we hand you an address that site has never seen, but the task is not bound to it.
{
"id": "c1d5cac4-c7e0-4a67-9030-07d6b921f54d",
"status": "waiting",
"service": "instagram",
"site": "",
"email": "quiet.harbor42@hotmail.com",
"price": "0.001",
"created_at": "2026-09-12T10:35:45Z",
"expires_at": "2026-09-12T10:40:45Z",
"cancellable_at": "2026-09-12T10:38:45Z",
"result": null
}
Same address again
Need a code on an address you used before, such as a login check or a password reset days or weeks later? Call
POST /v1/tasks/{id}/again with the id of an earlier task on that address. It opens a new task on the same
address. As with any task, only mail that arrives after the call counts: call it first, then have the site send the code.
It works for as long as we keep the task — 180 days — and its mailbox is still active. The earlier task
must be finished (received, timeout or cancelled); a task that is still waiting is
already listening on the address. The new task is charged like any task, except within 10 minutes of
the last charged code on that address (see Task lifecycle).
Lost the id? GET /v1/tasks?email=<address> lists your tasks on that address, newest first.
Without email it lists all of them. A page holds up to 100 (limit, default 50); send
next_cursor back as cursor for the next, older page, until it is null.
$ curl "https://api.mailotp.org/v1/tasks?email=quiet.harbor42@hotmail.com" -H "Authorization: Bearer mo_live_…" { "tasks": [ { "id": "c1d5cac4-…", "status": "received", "email": "quiet.harbor42@hotmail.com", … } ], "next_cursor": null } $ curl -X POST "https://api.mailotp.org/v1/tasks/c1d5cac4-…/again" -H "Authorization: Bearer mo_live_…"
When it cannot open, you get 409 mailbox_busy. The message says why: the address is serving another
task or has reached today's limit (retry later), or it is no longer active (create a new task instead).
404 not_found for an id you had means the task is older than 180 days and was deleted.
Long-poll & SSE
Don't hammer the API. Ask once with ?wait=30: the request blocks up to 30 seconds and returns the instant
the status changes. Or open /stream for Server-Sent Events. Keep one wait or stream per
task: a task accepts at most 8 open at once, and one more gets 429 too_many_waiters — it does
not count toward the failure pause.
Empty fields are omitted, not null. code and link appear only when that mail actually
carried one — a code-only mail has no link key at all, so read them with a presence check.
result itself is null until a mail lands.
$ curl "https://api.mailotp.org/v1/tasks/c1d5cac4-…?wait=30" -H "Authorization: Bearer mo_live_…" { "status": "received", "result": { "code": "483920", "from": "security@mail.instagram.com", "subject": "483920 is your Instagram code", "received_at": "2026-09-12T10:35:48Z", "confidence": "high" } } # SSE: one "status" event per change, ": ping" every 15s $ curl -N "https://api.mailotp.org/v1/tasks/c1d5cac4-…/stream" -H "Authorization: Bearer mo_live_…"
All endpoints
Errors & limits
Every error is JSON with a stable code and a request_id you can quote to support. Branch on
code, never on the message text — messages change, codes do not.
There is no requests-per-second limit. What is capped is failure: more than
500 failed requests in one UTC minute (HTTP 400 and above, except 429) pauses your API access
for 10 minutes, then 30 minutes, 1 hour and 24 hours if it repeats. It counts per account, across all keys. The
429 body says until when and Retry-After gives the seconds; only an admin can lift a pause
early. Successful calls never count toward it.
Watch it from your client. GET /v1/me returns pause.fail_limit_per_minute,
pause.failures_this_minute, pause.window_resets_at and pause.paused_until, and
keeps answering while you are paused.
{ "error": { "code": "insufficient_balance", "message": "balance is below the service price", "request_id": "1f0495b3…" } }