API Reference

Disposable email inboxes for AI agents and developers. Create temporary addresses, receive messages, and read content — all via REST. No browser required.

Base URL https://emptyinbox.me/api

Quick Start

Zero-config with MCP

Add to your MCP config and the server auto-registers on its first tool call — no setup needed. Package: emptyinbox-mcp on npm, listed in the official MCP Registry as io.github.cnohall/emptyinbox (source).

json
{
  "mcpServers": {
    "emptyinbox": {
      "command": "npx",
      "args": ["-y", "emptyinbox-mcp"]
    }
  }
}

REST API workflow

  1. 1Call POST /auth/register to get an API key (one-time setup)
  2. 2Call POST /inbox to create a disposable address
  3. 3Trigger signup or verification using that address
  4. 4Poll GET /messages until the email arrives
  5. 5Read code or action_url straight off the message — no parsing needed

Stuck? Tell us

If a call fails in a way retrying won't fix, a payment won't go through, or something is missing, send POST /feedback (MCP: report_issue). No API key needed. A human reads every report.

Authentication

API Key

All endpoints except POST /auth/register require an API key in the Authorization header:

http
Authorization: Bearer YOUR_API_KEY

Agents and scripts: call POST /auth/register below to get a key programmatically. No browser, no passkey, no email address.
Humans who want the web inbox: create a passkey account and read the key at Settings. Not required for API use.

Account

POST /auth/register No auth required

Create a new account and get an API key. The key is the account: keep it and no other credential is needed.

Accounts normally start with 5 inbox credits. Rather than refusing frequent registrations, the free grant is graded down: a network that has already taken several accounts in the last 24 hours gets fewer credits, and past that gets a working key with none, where POST /inbox answers 402 with purchase links. A network graded to none stays there until it has registered nothing for 7 days. Only extreme volume from one network is refused. Always read inbox_quota from the response.

Request Body

FieldTypeRequiredDescription
username string Optional 3–32 chars, letters/numbers/hyphens/underscores. Omit it, or send an empty body, and one is allocated for you.

Example Request

curl
# Simplest form: the server picks the username
curl -X POST "https://emptyinbox.me/api/auth/register" \
  -H "Content-Type: application/json" -d '{}'

# Or choose your own
curl -X POST "https://emptyinbox.me/api/auth/register" \
  -H "Content-Type: application/json" \
  -d '{"username": "my-agent"}'

Response

201 Created

json
{
  "api_key": "eiXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "username": "my-agent",
  "inbox_quota": 5
}

400 Bad Request — Invalid username

409 Conflict — Username already taken

429 Too Many Requests — Too many accounts from this network. A key you already hold still works.

A registration granted no free credits still returns 201, with inbox_quota set to 0 and a message explaining why.

GET /auth/me

Returns the authenticated account's username and remaining inbox quota.

Example Request

curl
curl "https://emptyinbox.me/api/auth/me" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response

200 OK

json
{
  "username": "my-agent",
  "inbox_quota": 5
}

Inbox Management

POST /inbox

Creates a new email inbox. Returns the email address as plain text, or JSON with ?format=json. The inbox expires 30 days after creation and then stops receiving mail; keep it if it will receive password resets. The expiry is also in the X-Inbox-Expires-At header. Each inbox consumes 1 from your quota. All accounts start with 5 inboxes. When quota runs out this returns 402 with links for buying more with Bitcoin. Humans can also buy at emptyinbox.me/inboxes.html.

Example Request

curl
curl -X POST "https://emptyinbox.me/api/inbox" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response

201 Created — Returns the email address as plain text

text
clever.sunny.butterfly@emptyinbox.me

402 Payment Required — Inbox quota exhausted; body carries bundles_url and quote_url

GET /inboxes

Returns all inboxes on this account, newest first. expires_at is null for a permanent inbox; expired inboxes no longer receive mail.

Example Request

curl
curl "https://emptyinbox.me/api/inboxes" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response

200 OK

json
[
  {
    "inbox": "clever.sunny.butterfly@emptyinbox.me",
    "created_at": "2026-10-01T10:00:00",
    "expires_at": "2026-10-31T10:00:00Z",
    "permanent": false,
    "expired": false
  },
  {
    "inbox": "quick.blue.elephant@emptyinbox.me",
    "created_at": "2026-09-01T09:00:00",
    "expires_at": null,
    "permanent": true,
    "expired": false
  }
]
POST /inbox/{address}/keep

Makes an inbox permanent for 30 credits, once. Use it when the address signed up for an account the user will keep: that account's password resets go there. Works on an expired inbox too, because an address is never given to anyone else. Calling it on an inbox that is already permanent costs nothing.

Example Request

curl
curl -X POST "https://emptyinbox.me/api/inbox/clever.sunny.butterfly@emptyinbox.me/keep" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response

200 OK

json
{
  "inbox": "clever.sunny.butterfly@emptyinbox.me",
  "expires_at": null,
  "permanent": true,
  "expired": false,
  "charged_credits": 30,
  "inbox_quota": 20,
  "message": "Inbox is now permanent."
}

402 Payment Required — Not enough credits; body carries credits_needed, credits_available and suggested_purchase.usd, the smallest amount to pass to POST /payments/quote

404 Not Found — No such inbox on this account

Message Management

GET /messages

Returns all messages across all inboxes, newest first. Messages are automatically deleted after 7 days. Poll this endpoint after triggering a signup or verification flow.

Example Request

curl
curl "https://emptyinbox.me/api/messages" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Fields (per message)

FieldTypeDescription
idstringUnique message identifier
inboxstringEmail address that received the message
subjectstringEmail subject line
senderstringSender email address
timestampintegerUnix timestamp
text_bodystringPlain text body
html_bodystringHTML body

Example Response

json
[
  {
    "id": "a1b2c3d4",
    "inbox": "clever.sunny.butterfly@emptyinbox.me",
    "subject": "Confirm your email address",
    "sender": "noreply@example.com",
    "timestamp": 1711234567,
    "text_body": "Your verification code is 482910",
    "html_body": "<p>Your verification code is <strong>482910</strong></p>"
  }
]
GET /message/{msgid}

Returns the full content of a specific message including all headers.

Path Parameters

ParameterTypeRequiredDescription
msgid string Required Message ID from GET /messages

Example Request

curl
curl "https://emptyinbox.me/api/message/a1b2c3d4" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Fields

FieldTypeDescription
recipientsarrayRecipient email addresses
headersobjectRaw email headers (Subject, From, Date, etc.)
text_bodystringPlain text body
html_bodystringHTML body
senderstringSender email address

Example Response

json
{
  "recipients": ["clever.sunny.butterfly@emptyinbox.me"],
  "headers": {
    "Subject": "Confirm your email address",
    "From": "noreply@example.com",
    "Date": "Mon, 1 Jan 2024 10:00:00 +0000"
  },
  "text_body": "Your verification code is 482910",
  "html_body": "<p>Your verification code is <strong>482910</strong></p>",
  "sender": "noreply@example.com"
}

404 Not Found — Message not found

Payments

Agents buy inbox quota with Bitcoin without leaving the API. Quota is credited as soon as the payment is seen on the network — there is no wait for confirmations, unless the transaction signals Replace-By-Fee.

GET /payments/bundles

Public price list. No authentication required.

Example Request

curl
curl "https://emptyinbox.me/api/payments/bundles"

Response

200 OK

json
{
  "currency": "USD",
  "default": "starter",
  "bundles": [
    { "id": "micro",    "quota": 10,  "usd": 1 },
    { "id": "starter",  "quota": 50,  "usd": 5 },
    { "id": "standard", "quota": 200, "usd": 15 },
    { "id": "bulk",     "quota": 750, "usd": 40 }
  ],
  "custom": {
    "min_usd": 1,
    "max_usd": 100,
    "note": "Pass usd instead of bundle to buy a whole-dollar amount."
  }
}
POST /payments/quote

Returns a Bitcoin address, the exact amount to send, and a BIP21 URI. Pass bundle for a named bundle, or usd for a whole-dollar amount between min_usd and max_usd; usd takes precedence over bundle. Idempotent per purchase: calling it again while a quote for the same bundle and amount is open returns the same address rather than issuing a new one. Quotes expire after 15 minutes.

Example Request

curl
curl -X POST "https://emptyinbox.me/api/payments/quote"   -H "Authorization: Bearer YOUR_API_KEY"   -H "Content-Type: application/json"   -d '{"bundle": "starter"}'

Response

201 Created

json
{
  "how_to_pay": "If you control a Bitcoin wallet and your user has approved the spend, send exactly 0.00005300 BTC to bc1qexampleaddress (or pay the bip21 URI). Otherwise give your user pay_url: a page with a QR code for any wallet. Then poll status_url with the same Authorization header; quota is credited as soon as the payment is seen.",
  "address": "bc1qexampleaddress",
  "bundle": "starter",
  "quota": 50,
  "usd": 5,
  "amount_satoshis": 5300,
  "amount_btc": "0.00005300",
  "bip21": "bitcoin:bc1qexampleaddress?amount=0.00005300",
  "pay_url": "https://emptyinbox.me/pay.html?address=bc1qexampleaddress",
  "expires_at": "2026-09-09T12:15:00Z",
  "status_url": "https://emptyinbox.me/api/payments/status/bc1qexampleaddress"
}

409 Conflict — a previous payment is still awaiting confirmation

GET /payments/status/{address}

quota_credited is what matters for creating inboxes — it can be true while settled is still false, because credit is granted at zero confirmations. revoked means a provisional credit was reversed after the transaction failed to confirm within 6 hours.

Example Request

curl
curl "https://emptyinbox.me/api/payments/status/bc1qexampleaddress"   -H "Authorization: Bearer YOUR_API_KEY"

Response

200 OK

json
{
  "address": "bc1qexampleaddress",
  "status": 0,
  "expected_satoshis": 5300,
  "received_satoshis": 5300,
  "quota": 50,
  "quota_credited": true,
  "settled": false,
  "revoked": false,
  "txid": "aed36253434b90e45ded86ccf1729f5d",
  "expires_at": "2026-09-09T12:15:00Z"
}

404 Not Found — no such quote for this account

Support

POST /feedback

Report a bug, a payment that won't go through, confusing docs or a missing feature. A human reads every report. Agents are welcome to call this themselves when they get stuck, or when a user decides not to buy and says why. The API key is optional; send it so the report is tied to your account. Never include passwords, verification codes or email contents.

Request Body

FieldTypeRequiredDescription
message string Required 10–4000 chars: what you were doing, what happened, what you expected.
category string Optional bug, payment, docs, feature_request or other (default).
tool string Optional The endpoint or MCP tool involved, e.g. buy_quota.
error string Optional The exact error text you saw.
context object Optional Small extra details, up to 4 KB of JSON.
contact string Optional Email for a reply, only if you want one.

Example Request

curl
curl -X POST "https://emptyinbox.me/api/feedback" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"category": "payment", "tool": "buy_quota", "message": "User has no Bitcoin wallet and wanted to pay by card"}'

Response

201 Created

json
{
  "id": "fb_3f9c1a2b7d04",
  "status": "received",
  "message": "Thanks. A human reads every report. Include \"contact\" if you want a reply."
}

400 Bad Request — Missing or too short message

429 Too Many Requests — More than 10 reports from one address in an hour

Error Codes

StatusDescriptionCommon Causes
401 Unauthorized Missing or invalid API key
402 Payment Required Inbox quota exhausted — buy more via POST /payments/quote
409 Conflict A previous Bitcoin payment is still awaiting confirmation
404 Not Found Message ID or inbox doesn't exist on this account
429 Too Many Requests Extreme registration volume from one network (200 accounts in 24 hours)

Limits & Notes

  • All accounts start with 5 inbox quota
  • Creating an inbox consumes 1 quota unit — agents buy more with Bitcoin via POST /api/payments/quote; humans at emptyinbox.me/inboxes.html
  • Messages are automatically deleted after 7 days
  • Inboxes expire 30 days after creation and stop receiving mail; keeping one costs 30 credits, once
  • Feedback: up to 10 reports per hour per address
  • Free starting quota is graded down per network after 3 accounts in 24 hours, and drops to none for 7 days after that — reuse your key
  • Full OpenAPI 3.1 spec available at /openapi.yaml

Integration Guides

FAQ

Why does POST /inbox return plain text instead of JSON?

The response body is the new address itself. Clients that assume JSON will fail to parse it, so read the body as text and trim it before use.

Is there an endpoint that waits for a message to arrive?

Not over REST. Poll GET /messages and filter on the inbox field. The MCP server exposes wait_for_message, which does that polling for you and blocks until the mail lands.

How do I get an API key?

Call POST /auth/register once and keep the key it returns, or install the MCP server, which registers on its first tool call. No passkey, browser or email address is involved, and a username is optional. Free starting quota is reduced for networks that have already created several accounts in the last 24 hours, so read inbox_quota off the response rather than assuming 5.

Does listing messages return mail from every inbox?

Yes. GET /messages returns everything on the account, with each message carrying the inbox it arrived at. Filter client-side when a test or agent owns several inboxes at once.

Can EmptyInbox send email?

No. It receives only. There is no send endpoint and no SMTP credential, so testing your own outbound mail needs a different tool.

How long are messages retained?

Seven days, then they are deleted automatically. Read anything you need to keep, and store it on your side.