← Home

API Documentation

Build on ZoroMail with a free public REST API. No developer account, no API key — just create a mailbox, grab a token, and read email.

https://zoromail.com/api/v1

Overview

The ZoroMail API is a free public REST API. There is no developer registration, no API key and no approval step: create a mailbox, exchange its password for a bearer token, and read mail — the same mailboxes and messages the website uses.

  • Base URL: https://DOMAIN/api/v1
  • Format: JSON everywhere, Content-Type: application/json.
  • Dates: ISO 8601 UTC, e.g. 2026-10-20T02:30:00+00:00.
  • IDs are returned as strings.
  • CORS is open (*), so browser apps can call the API directly. Authentication always uses the Bearer token — never cookies.
  • Method override: if a firewall blocks DELETE/PATCH, send POST with an X-HTTP-Method-Override: DELETE header or a ?_method=DELETE query parameter instead.
Pretty URLs need a 3-line nginx rule (CloudPanel: Sites → domain → Vhost)

Without it, the API still works via /api/index.php/v1/… or /api/?route=/v1/…. Add this for clean /api/v1/… URLs:

nginx
location /api/ {
    try_files $uri $uri/ /api/index.php?route=$uri&$args;
}

Quickstart

  1. Pick a domain from GET /domains.
  2. Create a mailbox with POST /accounts (address + password).
  3. Trade the password for a token at POST /token.
  4. Call mailbox endpoints with Authorization: Bearer <token>.
Example:
# 1. List domains
curl https://zoromail.com/api/v1/domains

# 2. Create a mailbox
curl -X POST https://zoromail.com/api/v1/accounts \
  -H "Content-Type: application/json" \
  -d '{"address":"demo@example.com","password":"secret123"}'

# 3. Get a token
TOKEN=$(curl -s -X POST https://zoromail.com/api/v1/token \
  -H "Content-Type: application/json" \
  -d '{"address":"demo@example.com","password":"secret123"}' \
  | python3 -c "import sys,json;print(json.load(sys.stdin)['token'])")

# 4. Read messages
curl https://zoromail.com/api/v1/messages \
  -H "Authorization: Bearer $TOKEN"

Authentication

Mailbox endpoints require the mailbox token in the Authorization header:

Authorization: Bearer 9f2c…64 hex chars…

Get a token with POST /token using the address + password you chose at creation. The token is a 64-character secret with the same power as the website login — keep it private. It stops working when the mailbox expires or is deleted.

Missing or invalid tokens return 401 {"error": "...", "code": "unauthorized"}. A token only ever grants access to its own mailbox.

Domains

GET/domainsList domains that can receive mail.
Response 200
[
  { "id": "1", "domain": "example.com", "isActive": true },
  { "id": "2", "domain": "mail.example.net", "isActive": true }
]

Accounts

POST/accountsCreate a mailbox. No signup.

Body: {"address": "name@example.com", "password": "secret123"} — the domain must be listed by GET /domains; the name follows the same rules as custom website addresses. Password: 8–72 chars (normalized to lowercase letters+digits, exactly like website logins, so the mailbox also works in the web UI).

Response 201
{
  "id": "42",
  "address": "name@example.com",
  "createdAt": "2026-10-20T02:30:00+00:00",
  "expiresAt": "2026-10-21T02:30:00+00:00"
}
POST/tokenTrade address + password for a bearer token.
Response 200
{ "id": "42", "token": "9f2c…64 hex chars…" }
GET/meThe authenticated mailbox. 🔒
GET/accounts/{id}Mailbox info; id must be your own. 🔒
DELETE/accounts/{id}Delete your mailbox and all its mail. 🔒 → 204

🔒 = requires Authorization: Bearer <token>.

Messages

GET/messages?page=1&limit=30Paginated inbox, newest first. 🔒
Response 200
{
  "messages": [
    {
      "id": "101",
      "accountId": "42",
      "from": { "name": "Acme", "address": "noreply@acme.test" },
      "subject": "Your code is 482910",
      "intro": "Your verification code is…",
      "seen": false,
      "hasAttachments": true,
      "size": 1240,
      "receivedAt": "2026-10-20T02:35:10+00:00"
    }
  ],
  "page": 1, "limit": 30, "total": 1
}
GET/messages/{id}Full message: text, sanitized HTML, attachments. 🔒
PATCH/messages/{id}Body {"seen": true} marks read/unread. 🔒
DELETE/messages/{id}Delete one message. 🔒 → 204

Reading a message via the website marks it read automatically; via the API it does not — use PATCH when you want that.

Attachments

Message detail includes an attachments array. Each entry carries a downloadUrl — a signed link bound to your mailbox token:

{
  "id": "7",
  "filename": "invoice.pdf",
  "contentType": "application/pdf",
  "size": 48210,
  "downloadUrl": "https://DOMAIN/download.php?token=9f2c…&id=7"
}

Files stream as Content-Disposition: attachment and are checked against your mailbox on every request.

Errors

All errors use HTTP status codes and a consistent envelope:

{ "error": "Invalid address or password.", "code": "invalid_credentials" }
StatusCodeMeaning
400invalid_address / invalid_domain / weak_password / invalid_bodyRequest validation failed.
401unauthorized / invalid_credentialsMissing/bad token or wrong password.
404not_foundUnknown endpoint, message, or someone else's resource.
405method_not_allowedWrong HTTP method for the endpoint.
409address_takenThe address is already in use.
413body_too_largeRequest body over 64 KiB.
429rate_limitedSlow down and retry later.

Rate limits

Limits are per IP address, per hour. They are generous for normal use and block abuse:

EndpointLimit
GET /domains120 / hour
POST /accounts, DELETE /accounts/{id}20 / hour
POST /token30 / hour
GET /messages…300 / hour
PATCH / DELETE /messages/{id}120 / hour

OpenAPI

A machine-readable OpenAPI 3.0 description is available for import into Postman, Insomnia or code generators:

https://zoromail.com/api-docs/openapi.json