LanguageGet API key →
REST · JSON · v1

Ghostbox API

Create disposable inboxes, wait for email and read messages programmatically — perfect for end-to-end testing sign-up and password-reset flows, QA automation and privacy tools.

Base URL
https://ghostbox.fun/api/v1
Format
JSON (UTF-8)
Auth
Bearer API key

Quick start: test a sign-up flow

3 steps
# 1. Create a fresh inbox
INBOX=$(curl -s -X POST https://ghostbox.fun/api/v1/inboxes -H "Authorization: Bearer $GHOSTBOX_KEY")
ID=$(echo $INBOX | jq -r .id); ADDRESS=$(echo $INBOX | jq -r .address)

# 2. Use $ADDRESS to sign up in the app you're testing, then wait (up to 60s) for the email
curl -s "https://ghostbox.fun/api/v1/inboxes/$ID/wait?timeout=60" -H "Authorization: Bearer $GHOSTBOX_KEY" | jq .message.otp_code

# 3. Done — "482913"

Authentication

Create an API key in your dashboard and send it in the Authorization header (or X-API-Key). Keys inherit your plan's retention, domains and limits. Keep them secret — they have full access to your inboxes.

Authorization: Bearer gbx_9fK2…

Errors & rate limits

Errors use conventional HTTP status codes and a consistent JSON body. Every API-key response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; daily quotas are reported in X-Quota-Remaining. On 429, wait for Retry-After seconds.

Error body
{
  "error": {
    "code": "inbox_limit",
    "message": "You've reached your limit of 25 active inboxes."
  }
}
400invalid_requestValidation failed
401invalid_api_keyMissing / revoked key
403plan_requiredFeature needs a higher plan
404inbox_not_foundDoesn't exist or expired
409address_takenAddress already in use
429rate_limited / quota_exceededSlow down

Inboxes

Create an inbox

POST/api/v1/inboxes

Creates a new inbox. Omit every field for a random address on the best domain your plan allows.

Body parameters
local string
Username before the @. Random if omitted. Letters, numbers, . _ -
domain string
Domain from GET /domains. Defaults to the best domain for your plan.
subdomain string
Pro+: creates user@{subdomain}.{domain}.
length integer
Length of a generated username (6–24). Default 12.
label string
Private label to help you organise inboxes.
password string
Lock the inbox so it can be opened from other devices with this password.
Request
curl -X POST "https://ghostbox.fun/api/v1/inboxes" \
  -H "Authorization: Bearer $GHOSTBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{"local":"my-test-user","domain":"ghostmail.dev","subdomain":null,"label":"Signup tests","password":null}'
Response
{
  "id": "3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44",
  "address": "calmhawk4445@ghostmail.dev",
  "local_part": "calmhawk4445",
  "host": "ghostmail.dev",
  "domain": "ghostmail.dev",
  "label": "Signup tests",
  "has_password": false,
  "retention_hours": 720,
  "created_at": "2026-11-02T09:14:03.120Z",
  "expires_at": "2026-12-02T09:14:03.120Z",
  "last_message_at": null,
  "message_count": 0,
  "unread_count": 0,
  "is_owner": true
}

List inboxes

GET/api/v1/inboxes

Returns all active inboxes you own.

Request
curl "https://ghostbox.fun/api/v1/inboxes" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
{
  "data": [
    {
      "id": "3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44",
      "address": "calmhawk4445@ghostmail.dev",
      "local_part": "calmhawk4445",
      "host": "ghostmail.dev",
      "domain": "ghostmail.dev",
      "label": "Signup tests",
      "has_password": false,
      "retention_hours": 720,
      "created_at": "2026-11-02T09:14:03.120Z",
      "expires_at": "2026-12-02T09:14:03.120Z",
      "last_message_at": null,
      "message_count": 0,
      "unread_count": 0,
      "is_owner": true
    }
  ]
}

Retrieve an inbox

GET/api/v1/inboxes/{id}

Returns a single inbox with message counts.

Request
curl "https://ghostbox.fun/api/v1/inboxes/3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
{
  "id": "3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44",
  "address": "calmhawk4445@ghostmail.dev",
  "local_part": "calmhawk4445",
  "host": "ghostmail.dev",
  "domain": "ghostmail.dev",
  "label": "Signup tests",
  "has_password": false,
  "retention_hours": 720,
  "created_at": "2026-11-02T09:14:03.120Z",
  "expires_at": "2026-12-02T09:14:03.120Z",
  "last_message_at": null,
  "message_count": 0,
  "unread_count": 0,
  "is_owner": true
}

Update an inbox

PATCH/api/v1/inboxes/{id}

Change the label or password, or extend the inbox's lifetime by a full retention period.

Body parameters
label string | null
New label.
password string | null
Set a password (min 4 chars) or null to remove it.
extend boolean
Reset expiry to now + your plan's retention.
Request
curl -X PATCH "https://ghostbox.fun/api/v1/inboxes/3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44" \
  -H "Authorization: Bearer $GHOSTBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label":"Prod smoke tests","extend":true}'
Response
{
  "id": "3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44",
  "address": "calmhawk4445@ghostmail.dev",
  "local_part": "calmhawk4445",
  "host": "ghostmail.dev",
  "domain": "ghostmail.dev",
  "label": "Prod smoke tests",
  "has_password": false,
  "retention_hours": 720,
  "created_at": "2026-11-02T09:14:03.120Z",
  "expires_at": "2026-12-02T09:14:03.120Z",
  "last_message_at": null,
  "message_count": 0,
  "unread_count": 0,
  "is_owner": true
}

Delete an inbox

DELETE/api/v1/inboxes/{id}

Permanently deletes the inbox and all of its messages.

Request
curl -X DELETE "https://ghostbox.fun/api/v1/inboxes/3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
{
  "deleted": true
}

Messages

List messages

GET/api/v1/inboxes/{id}/messages

Newest first. Use `before` with the last item's received_at to paginate.

Query parameters
limit integer
1–100, default 50.
before ISO date
Only messages received before this time.
since ISO date
Only messages received after this time.
unread boolean
Only unread messages.
Request
curl "https://ghostbox.fun/api/v1/inboxes/3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44/messages?limit=20" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
{
  "data": [
    {
      "id": "b7a5e1c9-2f34-4d3c-a8e2-91f0c6d5e4a1",
      "inbox_id": "3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44",
      "from_address": "no-reply@github.com",
      "from_name": "GitHub",
      "to_address": "calmhawk4445@ghostmail.dev",
      "subject": "Your GitHub launch code",
      "preview": "Here is your GitHub launch code: 48291037 …",
      "otp_code": "48291037",
      "verify_link": "https://github.com/account/verify?token=…",
      "has_attachments": false,
      "is_read": false,
      "size_bytes": 18234,
      "received_at": "2026-11-02T09:15:41.000Z",
      "expires_at": "2026-12-02T09:15:41.000Z"
    }
  ],
  "has_more": false
}

Retrieve a message

GET/api/v1/messages/{id}

Full message with text, original HTML, sanitized HTML (html_safe), headers and attachments. Marks it as read unless mark_read=false.

Query parameters
mark_read boolean
Default true.
remote boolean
Keep remote images in html_safe (they're blocked by default).
Request
curl "https://ghostbox.fun/api/v1/messages/b7a5e1c9-2f34-4d3c-a8e2-91f0c6d5e4a1" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
{
  "id": "b7a5e1c9-2f34-4d3c-a8e2-91f0c6d5e4a1",
  "inbox_id": "3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44",
  "from_address": "no-reply@github.com",
  "from_name": "GitHub",
  "to_address": "calmhawk4445@ghostmail.dev",
  "subject": "Your GitHub launch code",
  "preview": "Here is your GitHub launch code: 48291037 …",
  "otp_code": "48291037",
  "verify_link": "https://github.com/account/verify?token=…",
  "has_attachments": false,
  "is_read": true,
  "size_bytes": 18234,
  "received_at": "2026-11-02T09:15:41.000Z",
  "expires_at": "2026-12-02T09:15:41.000Z",
  "message_id": "<abc123@github.com>",
  "to_header": "calmhawk4445@ghostmail.dev",
  "cc_header": null,
  "reply_to": null,
  "text": "Here is your GitHub launch code: 48291037",
  "html": "<p>Here is your GitHub launch code: <b>48291037</b></p>",
  "html_safe": "<p>Here is your GitHub launch code: <b>48291037</b></p>",
  "remote_images": 0,
  "headers": [
    [
      "From",
      "GitHub <no-reply@github.com>"
    ],
    [
      "Subject",
      "Your GitHub launch code"
    ]
  ],
  "attachments": [
    {
      "index": 0,
      "filename": "invoice.pdf",
      "content_type": "application/pdf",
      "size": 48213,
      "cid": null,
      "inline": false,
      "url": "/api/v1/messages/b7a5e1c9-2f34-4d3c-a8e2-91f0c6d5e4a1/attachments/0"
    }
  ],
  "raw_url": "/api/v1/messages/b7a5e1c9-2f34-4d3c-a8e2-91f0c6d5e4a1/raw"
}

Mark read / unread

PATCH/api/v1/messages/{id}

Update the read state of a message.

Request
curl -X PATCH "https://ghostbox.fun/api/v1/messages/b7a5e1c9-2f34-4d3c-a8e2-91f0c6d5e4a1" \
  -H "Authorization: Bearer $GHOSTBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{"is_read":false}'
Response
{
  "id": "b7a5e1c9-2f34-4d3c-a8e2-91f0c6d5e4a1",
  "is_read": false
}

Delete a message

DELETE/api/v1/messages/{id}

Permanently deletes one message.

Request
curl -X DELETE "https://ghostbox.fun/api/v1/messages/b7a5e1c9-2f34-4d3c-a8e2-91f0c6d5e4a1" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
{
  "deleted": true
}

Delete all messages

DELETE/api/v1/inboxes/{id}/messages

Empties an inbox but keeps the address.

Request
curl -X DELETE "https://ghostbox.fun/api/v1/inboxes/3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44/messages" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
{
  "deleted": 12
}

Download raw source

GET/api/v1/messages/{id}/raw

Returns the original RFC 822 message (Content-Type: message/rfc822) — useful for DKIM checks or debugging.

Request
curl "https://ghostbox.fun/api/v1/messages/b7a5e1c9-2f34-4d3c-a8e2-91f0c6d5e4a1/raw" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
Received: from mail-sor-f41.google.com …
From: GitHub <no-reply@github.com>
…

Download an attachment

GET/api/v1/messages/{id}/attachments/{index}

Streams the attachment with its original filename and content type.

Request
curl "https://ghostbox.fun/api/v1/messages/b7a5e1c9-2f34-4d3c-a8e2-91f0c6d5e4a1/attachments/0" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
<binary file>

Real-time

Wait for the next email

GET/api/v1/inboxes/{id}/wait

Long-polls until a message arrives after since (default: now) or the timeout passes. Returns { message: null } on timeout. Ideal for end-to-end tests — no sleeps or polling loops.

Query parameters
timeout integer
Seconds to wait, 1–60. Default 30.
since ISO date
Return messages received after this time (default: request time).
Request
curl "https://ghostbox.fun/api/v1/inboxes/3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44/wait?timeout=60" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
{
  "message": {
    "id": "b7a5e1c9-2f34-4d3c-a8e2-91f0c6d5e4a1",
    "inbox_id": "3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44",
    "from_address": "no-reply@github.com",
    "from_name": "GitHub",
    "to_address": "calmhawk4445@ghostmail.dev",
    "subject": "Your GitHub launch code",
    "preview": "Here is your GitHub launch code: 48291037 …",
    "otp_code": "48291037",
    "verify_link": "https://github.com/account/verify?token=…",
    "has_attachments": false,
    "is_read": false,
    "size_bytes": 18234,
    "received_at": "2026-11-02T09:15:41.000Z",
    "expires_at": "2026-12-02T09:15:41.000Z"
  }
}

Stream new emails (SSE)

GET/api/v1/inboxes/{id}/stream

A Server-Sent Events stream that emits a message event with the message summary the instant mail arrives. Browsers can use EventSource; reconnect automatically when the stream closes (every 10 minutes).

Request
curl "https://ghostbox.fun/api/v1/inboxes/3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44/stream" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
event: ready
data: {"ok":true}

event: message
data: {"id":"b7a5e1c9-2f34-4d3c-a8e2-91f0c6d5e4a1","subject":"Your GitHub launch code","otp_code":"48291037","…":"…"}

Account & domains

List domains

GET/api/v1/domainsno auth needed

Domains available to you. `locked: true` means your plan needs an upgrade (premium domain names are masked until unlocked).

Request
curl "https://ghostbox.fun/api/v1/domains" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
{
  "data": [
    {
      "id": "c1d2…",
      "name": "ghostmail.dev",
      "min_rank": 0,
      "allow_subdomains": true,
      "private": false,
      "locked": false
    },
    {
      "id": "e3f4…",
      "name": "q•••••.com",
      "min_rank": 1,
      "allow_subdomains": true,
      "private": false,
      "locked": true
    }
  ],
  "mx_host": "mx.ghostbox.email"
}

Current account & usage

GET/api/v1/me

Your plan, limits and today's usage.

Request
curl "https://ghostbox.fun/api/v1/me" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
{
  "kind": "user",
  "user": {
    "id": "…",
    "email": "you@example.com",
    "role": "user"
  },
  "plan": {
    "id": "pro",
    "name": "Pro",
    "retention_hours": 720,
    "api_daily_quota": 100000,
    "api_rate_per_min": 300
  },
  "plan_expires_at": "2026-12-02T00:00:00.000Z",
  "usage": {
    "active_inboxes": 42,
    "api_requests_today": 1834,
    "webhooks": 1,
    "custom_domains": 0
  }
}

Public statistics

GET/api/v1/statsno auth needed

Service-wide counters. No authentication required.

Request
curl "https://ghostbox.fun/api/v1/stats" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
{
  "total_emails": 640233,
  "emails_today": 14283,
  "active_inboxes": 737,
  "domains": 12
}

Webhooks

Every delivery includes X-Ghostbox-Event, X-Ghostbox-Timestamp and X-Ghostbox-Signature: t=<ts>,v1=<hex>, where v1 = HMAC_SHA256(secret, "<ts>.<raw body>"). Reject events older than 5 minutes. Failed deliveries (non-2xx or timeout after 10s) are retried after 15s and 2 min; hooks are auto-disabled after 50 consecutive failures.

import crypto from "node:crypto";

app.post("/hooks/email", express.raw({ type: "application/json" }), (req, res) => {
  const [t, v1] = req.get("X-Ghostbox-Signature").split(",").map((p) => p.split("=")[1]);
  const expected = crypto.createHmac("sha256", process.env.GHOSTBOX_WEBHOOK_SECRET)
    .update(`${t}.${req.body}`).digest("hex");
  if (expected !== v1 || Date.now() / 1000 - Number(t) > 300) return res.sendStatus(401);

  const event = JSON.parse(req.body);
  console.log(event.data.message.subject, event.data.message.otp_code);
  res.sendStatus(200);
});

Create a webhook

POST/api/v1/webhooks

Pro+. We POST a `message.received` event to your URL for every new email (optionally only for one inbox). The signing secret is returned once.

Request
curl -X POST "https://ghostbox.fun/api/v1/webhooks" \
  -H "Authorization: Bearer $GHOSTBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/email","inbox_id":null}'
Response
{
  "id": "9a8b…",
  "url": "https://example.com/hooks/email",
  "inbox_id": null,
  "events": [
    "message.received"
  ],
  "is_active": true,
  "secret": "whsec_…"
}

List webhooks

GET/api/v1/webhooks

Your webhooks and their last delivery status.

Request
curl "https://ghostbox.fun/api/v1/webhooks" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
{
  "data": [
    {
      "id": "9a8b…",
      "url": "https://example.com/hooks/email",
      "is_active": true,
      "last_status": 200
    }
  ],
  "limit": 5
}

Send a test event

POST/api/v1/webhooks/{id}/test

Delivers a `webhook.test` event immediately.

Request
curl -X POST "https://ghostbox.fun/api/v1/webhooks/3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44/test" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
{
  "ok": true,
  "status": 200,
  "error": null
}

Delete a webhook

DELETE/api/v1/webhooks/{id}

Stops deliveries.

Request
curl -X DELETE "https://ghostbox.fun/api/v1/webhooks/3f0c1b2e-8a4d-4b8e-9c61-0d6a2f1e7b44" \
  -H "Authorization: Bearer $GHOSTBOX_KEY"
Response
{
  "deleted": true
}