API Reference

Base URL: https://waxseal.app/api/v1

Authentication

All API requests require an API key passed in the X-API-Key header. Create API keys from the portal (Settings → API Keys) or via the API.

Example request
curl -H "X-API-Key: wxs_your_api_key" \
  https://waxseal.app/api/v1/domains

Rate Limiting

Requests are rate-limited per API key. Every response includes rate limit headers:

ParameterTypeRequiredDescription
X-RateLimit-LimitintegerNoMaximum requests per window
X-RateLimit-RemainingintegerNoRequests remaining
X-RateLimit-Resetunix timestampNoWhen the window resets

When rate-limited, the API returns 429 Too Many Requests.

Idempotency

For POST requests, include an Idempotency-Key header to prevent duplicate operations. If the same key is sent again within 24 hours, the API returns the cached response.

Example
curl -X POST \
  -H "X-API-Key: wxs_..." \
  -H "Idempotency-Key: create-mailbox-john-123" \
  -H "Content-Type: application/json" \
  -d '{"local_part":"john","password":"securepass"}' \
  https://waxseal.app/api/v1/domains/example.com/mailboxes

Domains

POST/v1/domainsAdd a domain
ParameterTypeRequiredDescription
domainstringYesDomain name (e.g. example.com)
Request
{
  "domain": "example.com"
}
Response 201
{
  "id": "550e8400-...",
  "domain": "example.com",
  "status": "pending",
  "verification_token": "envelope-verify-a1b2c3",
  "dns_records": [
    {"type": "MX", "host": "example.com", "value": "10 mail.waxseal.app.", "status": "pending"},
    {"type": "TXT", "host": "example.com", "value": "v=spf1 include:mail.waxseal.app ~all", "status": "pending"},
    {"type": "TXT", "host": "mail._domainkey.example.com", "value": "v=DKIM1; k=rsa; p=...", "status": "pending"},
    {"type": "TXT", "host": "_dmarc.example.com", "value": "v=DMARC1; p=quarantine; ...", "status": "pending"},
    {"type": "TXT", "host": "_envelope-verify.example.com", "value": "envelope-verify-a1b2c3", "status": "pending"}
  ],
  "created_at": "2026-03-21T10:00:00Z"
}
GET/v1/domainsList all domains
POST/v1/domains/{domain}/verifyTrigger DNS verification
DELETE/v1/domains/{domain}Delete domain and all its data

Mailboxes

POST/v1/domains/{domain}/mailboxesCreate a mailbox
ParameterTypeRequiredDescription
local_partstringYesPart before @ (1-64 chars)
passwordstringYesIMAP/SMTP password (min 8 chars)
quota_mbintegerNoStorage quota in MB (0 = plan default)
Request
{
  "local_part": "john",
  "password": "securepassword123",
  "quota_mb": 1024
}
Response 201
{
  "id": "660e8400-...",
  "domain": "example.com",
  "local_part": "john",
  "address": "[email protected]",
  "quota_mb": 1024,
  "created_at": "2026-03-21T10:10:00Z"
}
GET/v1/domains/{domain}/mailboxesList mailboxes
PATCH/v1/domains/{domain}/mailboxes/{user}Update mailbox (password, quota)
DELETE/v1/domains/{domain}/mailboxes/{user}Delete mailbox

Aliases

POST/v1/domains/{domain}/aliasesCreate an alias
ParameterTypeRequiredDescription
local_partstringYesPart before @ (use * for catch-all)
destinationsstring[]YesOne or more email addresses to forward to
Request
{
  "local_part": "info",
  "destinations": ["[email protected]", "[email protected]"]
}
GET/v1/domains/{domain}/aliasesList aliases
DELETE/v1/domains/{domain}/aliases/{alias}Delete alias

Groups

Groups are distribution lists. Email sent to the group address is delivered to all members.

POST/v1/domains/{domain}/groupsCreate a group
ParameterTypeRequiredDescription
local_partstringYesGroup address local part
Response 201
{
  "id": "880e8400-...",
  "domain": "example.com",
  "local_part": "engineering",
  "address": "[email protected]",
  "display_name": "engineering",
  "moderated": false,
  "reply_to_mode": "group",
  "member_count": 0,
  "created_at": "2026-03-21T10:20:00Z"
}
GET/v1/domains/{domain}/groupsList groups with member counts
POST/v1/domains/{domain}/groups/{group}/membersAdd a member
ParameterTypeRequiredDescription
addressstringYesMember email address
is_ownerbooleanNoWhether the member is a group owner (default false)
DELETE/v1/domains/{domain}/groups/{group}/members/{address}Remove a member

Analytics

GET/v1/analytics/deliveryDelivery statistics
ParameterTypeRequiredDescription
domainstringNoFilter to a specific domain
fromYYYY-MM-DDNoStart date (default: 30 days ago, max: 90 days)
toYYYY-MM-DDNoEnd date (default: today)
Response 200
{
  "delivery": [
    {
      "domain": "example.com",
      "day": "2026-03-21",
      "sent": 42,
      "delivered": 40,
      "bounced": 1,
      "deferred": 1
    }
  ]
}

Webhooks

Receive HTTP notifications when events occur.

Supported Events

mailbox.createdA new mailbox was created
mailbox.deletedA mailbox was deleted
domain.verifiedA domain passed DNS verification
invoice.paidA subscription payment was processed
subscription.cancelledA subscription was cancelled
POST/v1/webhooksCreate a webhook
Request
{
  "url": "https://your-app.com/webhook",
  "events": ["mailbox.created", "domain.verified"]
}
Signature verification: Each webhook delivery includes an X-Envelope-Signature header containing an HMAC-SHA256 hex digest of the request body, signed with a per-webhook secret.
GET/v1/webhooksList webhooks
DELETE/v1/webhooks/{id}Delete a webhook

API Keys

POST/v1/api-keysCreate an API key
ParameterTypeRequiredDescription
namestringYesHuman-readable name
scopesstring[]NoPermissions (default: ["*"])
expires_atRFC3339NoExpiration timestamp

Available Scopes

*keys:readkeys:writedomains:readdomains:writemailboxes:readmailboxes:writealiases:readaliases:writegroups:readgroups:writeanalytics:readwebhooks:readwebhooks:write
Important: The raw API key is only returned on creation. Store it securely — it cannot be retrieved again.
GET/v1/api-keysList API keys (without raw values)
DELETE/v1/api-keys/{id}Revoke an API key

Errors

All errors follow the same format:

{
  "error": {
    "code": "not_found",
    "message": "domain not found"
  }
}

Error Codes

CodeHTTPDescription
bad_request400Invalid request body or parameters
unauthorized401Missing or invalid API key
forbidden403Insufficient permissions
not_found404Resource not found
duplicate409Resource already exists
rate_limited429Rate limit exceeded
internal_error500Unexpected server error
service_suspended503Service temporarily unavailable

Health Check

GET /api/healthz — Returns {"status":"ok"}. No authentication required. Use for uptime monitoring.