REST API
Public REST API for managing domains, users, aliases, DKIM, queue, services, stats, and DNS programmatically.
CeyMail exposes a versioned REST API for programmatic access to your mail server. Automate provisioning, integrate with billing systems like WHMCS, or build custom tooling.
Business feature
The REST API is available on the Business plan and above.
Authentication
All API requests require a Bearer token in the Authorization header:
curl https://mc.example.com/api/v1/domains \
-H "Authorization: Bearer ceymail_your_token_here"
Creating an API key
- Go to Settings > API in the dashboard
- Click Create Key
- Enter a name (e.g., "WHMCS Production")
- Set permissions for each resource (None / Read / Read-Write)
- Click Create Key — copy the token immediately (it is only shown once)
Scoped permissions
Each API key has granular read/write permissions per resource:
| Resource | Read | Write |
|---|---|---|
| Domains | List domains | Create, delete |
| Users | List users | Create, update password, delete |
| Aliases | List aliases | Create, delete |
| DKIM | List keys | Generate, delete |
| Queue | Stats, list items | Flush, delete messages |
| Stats | System metrics | — (read-only) |
| Services | List status | Start, stop, restart |
| DNS | Records, verify | — (read-only) |
Write scope includes read. Stats and DNS are read-only resources — they cannot be granted write scope. DNS verify (POST) requires only read scope since it performs lookups, not data mutation.
Key limits
- Business plan: up to 5 active keys
- Enterprise plan: up to 25 active keys
Keys can be revoked and have their scopes updated at any time from the dashboard.
Base URL
https://your-dashboard/api/v1/
All endpoints return JSON. Successful responses use HTTP 200 or 201. Errors return a JSON object with an error field.
Request format
GETrequests use query parameters for pagination, search, and filteringPOSTandPATCHrequests requireContent-Type: application/jsonheader (even for requests with no body, such as queue flush)DELETErequests use path parameters for the resource ID (no Content-Type required)
Rate limiting
API requests are rate-limited per key:
- Business: 120 requests/minute
- Enterprise: 300 requests/minute
Exceeding the limit returns HTTP 429 with a Retry-After header.
Pagination
List endpoints return paginated results:
{
"domains": [...],
"totalItems": 42,
"page": 1,
"pageSize": 10,
"totalPages": 5
}
Query parameters:
| Parameter | Default | Max | Description |
|---|---|---|---|
page | 1 | — | Page number |
pageSize | 10 | 100 | Results per page |
search | — | — | Search term (searches primary field) |
sortField | varies | — | Column to sort by |
sortDir | asc | — | asc or desc |
Domains
List domains
GET /api/v1/domains
GET /api/v1/domains?search=example&page=1&pageSize=20&sortField=name&sortDir=asc
Response:
{
"domains": [
{ "id": 1, "name": "example.com", "created_at": "2026-03-01T..." }
],
"totalItems": 1,
"page": 1,
"pageSize": 10,
"totalPages": 1
}
Create domain
POST /api/v1/domains
Content-Type: application/json
{ "name": "newdomain.com" }
Response (201):
{ "domain": { "id": 2, "name": "newdomain.com", "created_at": "..." } }
Delete domain
DELETE /api/v1/domains/2
Response: { "message": "Domain deleted" }
Returns 409 if the domain has associated users or aliases.
Users
List users
GET /api/v1/users
GET /api/v1/users?domain=example.com&search=john
Response:
{
"users": [
{
"id": 1,
"domain_id": 1,
"email": "john@example.com",
"created_at": "2026-03-01T...",
"domain_name": "example.com"
}
],
"totalItems": 1,
"page": 1,
"pageSize": 10,
"totalPages": 1
}
Create user
POST /api/v1/users
Content-Type: application/json
{ "email": "john@example.com", "password": "YourSecurePassword" }
The domain in the email must already exist. Passwords must meet complexity requirements (minimum 8 characters, mixed case, numbers, symbols).
Response (201):
{
"user": {
"id": 2,
"domain_id": 1,
"email": "john@example.com",
"created_at": "...",
"domain_name": "example.com"
}
}
Update password
PATCH /api/v1/users/5
Content-Type: application/json
{ "password": "NewSecurePassword" }
Response: { "message": "Password updated" }
Delete user
DELETE /api/v1/users/5
Response: { "message": "User deleted" }
Aliases
List aliases
GET /api/v1/aliases
GET /api/v1/aliases?search=info
Response:
{
"aliases": [
{
"id": 1,
"domain_id": 1,
"source": "info@example.com",
"destination": "john@example.com",
"created_at": "...",
"domain_name": "example.com"
}
],
"totalItems": 1,
"page": 1,
"pageSize": 10,
"totalPages": 1
}
Create alias
POST /api/v1/aliases
Content-Type: application/json
{ "source": "info@example.com", "destination": "john@example.com" }
The source domain must already exist.
Response (201):
{
"alias": {
"id": 1,
"domain_id": 1,
"source": "info@example.com",
"destination": "john@example.com",
"created_at": "...",
"domain_name": "example.com"
}
}
Delete alias
DELETE /api/v1/aliases/3
Response: { "message": "Alias deleted" }
DKIM
List DKIM keys
GET /api/v1/dkim
Response:
{
"dkim": [
{
"domain": "example.com",
"selector": "mail",
"hasKey": true,
"dnsRecord": "mail._domainkey IN TXT \"v=DKIM1; k=rsa; p=MIIBIj...\""
}
]
}
Generate DKIM key
POST /api/v1/dkim
Content-Type: application/json
{ "domain": "example.com" }
Response (201):
{ "message": "DKIM key generated", "domain": "example.com", "selector": "mail" }
Delete DKIM key
DELETE /api/v1/dkim/example.com
Response: { "message": "DKIM key deleted" }
Queue
Queue statistics
GET /api/v1/queue
Response:
{ "queue": { "active": 0, "deferred": 2, "bounce": 0, "hold": 0, "total": 2 } }
List queue items
GET /api/v1/queue/items
Response:
{
"items": [
{
"id": "A1B2C3D4E5",
"sender": "user@example.com",
"recipients": ["recipient@other.com"],
"size": 1234,
"arrivalTime": "2026-03-22T...",
"queueName": "deferred",
"reason": "Connection timed out"
}
],
"total": 1
}
Flush queue
Force delivery retry for all deferred messages:
POST /api/v1/queue/flush
Content-Type: application/json
Response: { "message": "Queue flushed" }
Delete message
DELETE /api/v1/queue/A1B2C3D4E5
Queue IDs are hex strings (e.g., A1B2C3D4E5).
Response: { "message": "Message deleted" }
Stats
System metrics
GET /api/v1/stats
Response:
{
"cpu_percent": 12.5,
"memory_used_bytes": 1073741824,
"memory_total_bytes": 4294967296,
"disk_used_bytes": 21474836480,
"disk_total_bytes": 53687091200,
"mail_queue_size": 2,
"services_healthy": 7,
"services_total": 7
}
Services
List services
GET /api/v1/services
Response:
{
"services": [
{
"name": "postfix",
"status": "running",
"uptime_seconds": 86400,
"memory_bytes": 52428800,
"pid": 1234
}
]
}
Control a service
POST /api/v1/services/postfix/restart
Content-Type: application/json
Valid actions: start, stop, restart.
Response:
{
"message": "Service restart completed",
"service": { "name": "postfix", "status": "running", "uptime_seconds": 5, "memory_bytes": 52428800, "pid": 5678 }
}
AI filter
The ceymail-ai-filter service cannot be controlled via the API. Use the AI Screening settings page instead.
DNS
List DNS records
GET /api/v1/dns/example.com
The domain must exist in your configured domains.
Response:
{
"domain": "example.com",
"records": [
{
"type": "MX",
"name": "example.com",
"expected": null,
"current": "mail.example.com",
"verified": true
},
{
"type": "SPF",
"name": "example.com",
"expected": null,
"current": "v=spf1 ...",
"verified": true
},
{
"type": "DKIM",
"name": "mail._domainkey.example.com",
"expected": null,
"current": "v=DKIM1; ...",
"verified": true
},
{
"type": "DMARC",
"name": "_dmarc.example.com",
"expected": null,
"current": "v=DMARC1; ...",
"verified": true
},
{
"type": "A",
"name": "example.com",
"expected": null,
"current": "1.2.3.4",
"verified": true
}
],
"score": 100
}
Verify DNS records
Force a fresh DNS lookup:
POST /api/v1/dns/example.com/verify
Content-Type: application/json
Returns the same response format as the GET endpoint with fresh lookup results.
Errors
All errors return a consistent format:
{ "error": "Domain already exists" }
| Status | Meaning |
|---|---|
| 200 | Success (read or action) |
| 201 | Created |
| 400 | Invalid input |
| 401 | Missing, invalid, or insufficient token |
| 404 | Resource not found |
| 405 | Method not allowed |
| 409 | Conflict (duplicate or FK constraint) |
| 415 | Missing Content-Type on POST/PATCH |
| 429 | Rate limit exceeded |
| 500 | Server error |
Quick start
# Set your token
TOKEN='your_api_key_here'
BASE='https://mc.example.com/api/v1'
# List domains
curl -s "$BASE/domains" -H "Authorization: Bearer $TOKEN"
# Create a domain
curl -s -X POST "$BASE/domains" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"newdomain.com"}'
# Create users (use strong, unique passwords)
for u in 'john' 'jane' 'admin'; do
curl -s -X POST "$BASE/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"email":"'"$u"'@newdomain.com","password":"'"$(openssl rand -base64 16)"'"}'
done
# Check DNS records
curl -s "$BASE/dns/newdomain.com" -H "Authorization: Bearer $TOKEN"
# Check server stats
curl -s "$BASE/stats" -H "Authorization: Bearer $TOKEN"