Three endpoints, JSON in and JSON out. No key and no account on public
domains — paste a request into a terminal and it works.
Overview
A mailbox is never created — it exists the moment a message arrives at an
address, and is gone 5 days later. There is
nothing to register, so on public domains the API has no concept of a
user, a project or a token.
Every response is JSON, including every error.
All times are UTC in RFC 3339 form — 2026-08-04T18:31:07Z.
Message ids are opaque strings. Do not parse them.
Receive only. There is no endpoint that sends mail, by design.
Base URL
https://grabmail.io/api/v1
HTTPS only; plain HTTP is redirected. The version lives in the path, and
v1 will not change shape under you — a breaking change gets
a new number.
Authentication: none
None on public domains. Anyone who knows an address can
read its mailbox, through the API exactly as through the website. That is
the bargain a shared disposable service makes, so never point anything
you care about at a public address.
A domain you point here answers on these same endpoints, with no key
either. Point the MX at us and the first message connects it; see
connecting a domain. Mailboxes on it are readable by anyone who knows the address,
exactly like the public domains.
One case still carries a header: a domain we have closed on request is
read with Authorization: Bearer <key>, and a wrong or
missing key answers 401 with unauthorized. Keys
are compared in constant time, so a wrong one takes as long to reject as a
right one takes to accept.
Premium domains
The one exception to the rule above. The public domains
are on the public disposable-mail blocklists, which is why a signup form
sometimes refuses an address on them. A paid plan opens a pool of
92 private .com domains, kept off those lists.
Nothing about the API changes. Same paths, same
parameters, same response shapes. The only difference is one header:
a premium address is read with
Authorization: Bearer gm_live_…, using a key from
your API keys. Without a valid key the same request answers
402 or 403 — never a mailbox.
# A public domain: no header at all.
curl -sG https://grabmail.io/api/v1/mailbox \
--data-urlencode "address=a7f3k2@grabmail.io"
# A premium domain: the same call, plus a key.
curl -sG https://grabmail.io/api/v1/mailbox \
-H "Authorization: Bearer gm_live_…" \
--data-urlencode "address=a7f3k2@one-of-the-pool.com"
The public domains and any domain of your own stay free, keyless and
unlimited on every plan, including the free one. Quotas count messages
that arrive on the premium pool and nothing else. Plans and prices are
on the plans page.
Endpoints
GET/api/v1/mailbox
Everything waiting at an address, newest first. This is the call your test suite polls.
Parameters
Name
In
Type
Required
Description
address
query
string
yes
The mailbox to read, e.g. k7fq2m@grabmail.io.
limit
query
integer
no
How many messages to return in this call, 1–200. Defaults to 50, newest first. It caps one response, not the mailbox — use before to read past it.
before
query
string
no
The id of the oldest message you already have; returns the page after it. Pass back the next field from the previous response. When next is null, you have everything.
Deleted. The call is idempotent: deleting twice still answers 200.
400
mailbox is missing or invalid.
404
No such message in that mailbox.
429
Rate limit exceeded.
Attachments
Every message lists its attachments with a ready-made URL. Fetch it with
the same authorisation as the message itself.
GET /api/v1/attachment/{id}?mailbox={address}
It always answers application/octet-stream with
Content-Disposition: attachment, whatever the sender labelled
it. That is deliberate: echoing a stranger's text/html back
would let an attachment run as a page on this origin. The real type is in
the message JSON, where it is data rather than an instruction.
Errors
Every failure is JSON with the same two fields, so a client handles them
in one place. The status carries the category, error is a
stable machine-readable slug, and message is for humans and
may be reworded at any time.
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "invalid_address",
"message": "address must look like name@domain"
}
Never branch on message. The slugs in use are
invalid_address, unknown_domain,
not_found and rate_limited.
Rate limits
One request per second, per address. Polling a mailbox
once a second is the intended pattern and is never throttled.
Over the limit you get 429 with Retry-After in
seconds. There is no daily quota and no burst credit to manage.
Retention
A message is deleted 5 days
after it arrives, read or not. Every message carries
expires_at, so you never compute that date yourself.
It is a hard limit, not a setting — no parameter extends it. If a message
has to outlive the window, fetch it and store it on your side.
Your own domain
Point your MX at smtp.grabmail.io and every address on
your domain answers through these same endpoints — no second API to learn,
no registration, and no key.