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/domainsRate Limiting
Requests are rate-limited per API key. Every response includes rate limit headers:
| Parameter | Type | Required | Description |
|---|---|---|---|
| X-RateLimit-Limit | integer | No | Maximum requests per window |
| X-RateLimit-Remaining | integer | No | Requests remaining |
| X-RateLimit-Reset | unix timestamp | No | When 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/mailboxesDomains
POST
/v1/domains— Add a domain| Parameter | Type | Required | Description |
|---|---|---|---|
| domain | string | Yes | Domain 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/domains— List all domainsPOST
/v1/domains/{domain}/verify— Trigger DNS verificationDELETE
/v1/domains/{domain}— Delete domain and all its dataMailboxes
POST
/v1/domains/{domain}/mailboxes— Create a mailbox| Parameter | Type | Required | Description |
|---|---|---|---|
| local_part | string | Yes | Part before @ (1-64 chars) |
| password | string | Yes | IMAP/SMTP password (min 8 chars) |
| quota_mb | integer | No | Storage 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}/mailboxes— List mailboxesPATCH
/v1/domains/{domain}/mailboxes/{user}— Update mailbox (password, quota)DELETE
/v1/domains/{domain}/mailboxes/{user}— Delete mailboxAliases
POST
/v1/domains/{domain}/aliases— Create an alias| Parameter | Type | Required | Description |
|---|---|---|---|
| local_part | string | Yes | Part before @ (use * for catch-all) |
| destinations | string[] | Yes | One or more email addresses to forward to |
Request
{
"local_part": "info",
"destinations": ["[email protected]", "[email protected]"]
}GET
/v1/domains/{domain}/aliases— List aliasesDELETE
/v1/domains/{domain}/aliases/{alias}— Delete aliasGroups
Groups are distribution lists. Email sent to the group address is delivered to all members.
POST
/v1/domains/{domain}/groups— Create a group| Parameter | Type | Required | Description |
|---|---|---|---|
| local_part | string | Yes | Group 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}/groups— List groups with member countsPOST
/v1/domains/{domain}/groups/{group}/members— Add a member| Parameter | Type | Required | Description |
|---|---|---|---|
| address | string | Yes | Member email address |
| is_owner | boolean | No | Whether the member is a group owner (default false) |
DELETE
/v1/domains/{domain}/groups/{group}/members/{address}— Remove a memberAnalytics
GET
/v1/analytics/delivery— Delivery statistics| Parameter | Type | Required | Description |
|---|---|---|---|
| domain | string | No | Filter to a specific domain |
| from | YYYY-MM-DD | No | Start date (default: 30 days ago, max: 90 days) |
| to | YYYY-MM-DD | No | End 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.created | A new mailbox was created |
| mailbox.deleted | A mailbox was deleted |
| domain.verified | A domain passed DNS verification |
| invoice.paid | A subscription payment was processed |
| subscription.cancelled | A subscription was cancelled |
POST
/v1/webhooks— Create a webhookRequest
{
"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/webhooks— List webhooksDELETE
/v1/webhooks/{id}— Delete a webhookAPI Keys
POST
/v1/api-keys— Create an API key| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Human-readable name |
| scopes | string[] | No | Permissions (default: ["*"]) |
| expires_at | RFC3339 | No | Expiration 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-keys— List API keys (without raw values)DELETE
/v1/api-keys/{id}— Revoke an API keyErrors
All errors follow the same format:
{
"error": {
"code": "not_found",
"message": "domain not found"
}
}Error Codes
| Code | HTTP | Description |
|---|---|---|
| bad_request | 400 | Invalid request body or parameters |
| unauthorized | 401 | Missing or invalid API key |
| forbidden | 403 | Insufficient permissions |
| not_found | 404 | Resource not found |
| duplicate | 409 | Resource already exists |
| rate_limited | 429 | Rate limit exceeded |
| internal_error | 500 | Unexpected server error |
| service_suspended | 503 | Service temporarily unavailable |
Health Check
GET /api/healthz — Returns {"status":"ok"}. No authentication required. Use for uptime monitoring.