API reference · v1
HTTP API
REST API for managing domains, dedicated IPs, and mailboxes, and for sending and retrieving email through the tenant’s own SMTP and IMAP servers. Requests and responses are JSON.
Quickstart
Send an email from an existing mailbox.
- Create an API key at
/dashboard/api-keys. The plaintext is shown once; only its hash is stored. - Retrieve the mailbox ID with
GET /mailboxes. - Submit the message with
POST /mailboxes/<id>/send.
curl https://adbtd.com/api/v1/mailboxes/MAILBOX_ID/send \
-H "Authorization: Bearer adbtd_live_…" \
-H "Content-Type: application/json" \
-d '{
"to": "you@example.com",
"subject": "first send from adbtd",
"text": "Hello from my own IP."
}'Authentication
All requests require an Authorization header containing a Bearer token. Tokens are scoped to a single workspace and grant full permissions on that workspace. There is no OAuth flow or user-level scope in v1. Keys can be revoked from the dashboard.
Authorization: Bearer adbtd_live_8JeTvR2xN…Tokens have the format adbtd_live_ followed by a 32-byte URL-safe random string. The first 8 characters after the prefix are displayed in the dashboard for identification; the rest of the token is secret.
Base URL and request format
- Base URL
- https://adbtd.com/api/v1
- Content-Type
- application/json
- Encoding
- UTF-8
Successful responses return { "data": ... }. Errors return { "error": { "code": "...", "message": "..." } } with an HTTP status code that reflects the type of failure. Some error responses include additional fields, such as checkoutUrl on a 402 payment_required.
Errors
Error responses include a stable string code for programmatic handling and a human-readable message.
| Status | Code | Description |
|---|---|---|
| 400 | validation_error | Request body or parameters failed validation. |
| 401 | unauthorized | Missing, malformed, or revoked Bearer token. |
| 402 | payment_required | Workspace has no saved payment method. |
| 404 | not_found | The resource does not exist on this workspace. |
| 409 | conflict | Uniqueness or state precondition failed. |
| 429 | rate_limited | Rate limit exceeded. See the Retry-After header. |
| 502 | smtp_error | The tenant SMTP server rejected the message. |
| 502 | imap_error | The tenant IMAP server rejected the connection or fetch. |
| 503 | inventory_unavailable | No free dedicated IPs available. |
| 500 | internal_error | Unexpected server error. |
Rate limits
Rate limits are applied per API key using a sliding token bucket. Responses with status 429 include a Retry-After header indicating the number of seconds to wait before retrying.
| Bucket | Rate | Applies to |
|---|---|---|
| default | 60 req/min, burst 60 | All CRUD endpoints. |
| send | 30 req/min, burst 10 | POST /mailboxes/:id/send. |
| fetch | 30 req/min, burst 30 | GET /mailboxes/:id/messages and /messages/:uid. |
Endpoints
Endpoints are grouped by resource. Path parameters are written :id; substitute the resource’s id from the corresponding list endpoint.
Workspace
/meReturns the workspace associated with the API key. Useful for verifying authentication.
Response · 200
{
"data": {
"workspace": {
"id": "ws_…",
"slug": "acme",
"name": "Acme",
"currency": "usd"
},
"apiKey": { "id": "key_…", "name": "ci/cd" }
}
}Domains
/domainsReturns all domains on the workspace, including verification flags for each DNS record type.
Response · 200
{
"data": [
{
"id": "dom_…",
"name": "acme.com",
"mode": "byo",
"status": "verified",
"spfOk": true, "dkimOk": true, "dmarcOk": true, "mxOk": true,
"dkimSelector": "adbtd-2026",
"createdAt": "2026-04-01T12:00:00.000Z",
"lastVerifiedAt": "2026-05-03T09:21:14.221Z"
}
]
}/domainsAdds a domain to the workspace. To add mailboxes use POST /mailboxes; to add IPs use POST /ips. The workspace must already have at least one dedicated IP, which is provisioned through the first order placed via the dashboard. Extra fields in the request body return a 400 response.
Request body
{
"name": "acme.com", // required, valid FQDN
"mode": "byo" // "byo" (default) or "buy"
}Response · 200
{
"data": {
"orderId": "ord_…",
"status": "pending_provision",
"domain": "acme.com"
}
}/domains/:idPermanently deletes the domain and any mailboxes belonging to it. The Stripe subscription quantity is adjusted automatically.
Response · 200
{ "data": { "deletedMailboxes": 2 } }Dedicated IPs
/ipsReturns dedicated IPs assigned to the workspace.
Response · 200
{
"data": [
{
"id": "ip_…",
"address": "203.0.113.7",
"status": "assigned",
"allocatedAt": "2026-04-01T12:01:30.000Z",
"lastReputationAt": "2026-05-02T00:00:00.000Z",
"reputationFlags": null
}
]
}/ipsOrders additional dedicated IPs. Requires at least one domain on the workspace.
Request body
{ "count": 1 } // 1–10Response · 200
{
"data": {
"orderId": "ord_…",
"status": "pending_provision",
"ips": 1
}
}/ips/:idReleases a dedicated IP. The request is refused if releasing the IP would leave active mailboxes without an assigned IP. Released IPs enter a cooldown period before being reused.
Response · 200
{
"data": {
"released": "203.0.113.7",
"cancelledSubscription": false
}
}Mailboxes
/mailboxesReturns all mailboxes on the workspace, including the SMTP and IMAP hosts and ports the client should connect to.
Response · 200
{
"data": [
{
"id": "mb_…",
"email": "alice@acme.com",
"smtpHost": "203.0.113.7",
"imapHost": "203.0.113.7",
"submissionPort": 587,
"imapPort": 993,
"status": "active",
"createdAt": "2026-05-01T10:14:00.000Z"
}
]
}/mailboxesCreates one or more mailboxes. The domain of each email must already exist on the workspace; if it does not, call POST /domains first.
Request body
{
"emails": [
"alice@acme.com",
"bob@acme.com"
]
}Response · 200
{
"data": {
"orderId": "ord_…",
"status": "pending_provision",
"emails": ["alice@acme.com", "bob@acme.com"]
}
}/mailboxes/:idReturns connection details for a single mailbox. The plaintext password is not returned by the API; it is available in the dashboard.
Response · 200
{
"data": {
"id": "mb_…",
"email": "alice@acme.com",
"smtpHost": "203.0.113.7",
"imapHost": "203.0.113.7",
"submissionPort": 587,
"imapPort": 993,
"status": "active",
"createdAt": "2026-05-01T10:14:00.000Z"
}
}/mailboxes/:idPermanently deletes a mailbox. Removes it from the Dovecot password file and adjusts the Stripe subscription.
Response · 200
{ "data": { "deleted": "alice@acme.com" } }Sending
/mailboxes/:id/sendSubmits an outbound message through the mailbox's SMTP submission server. The message is sent from the workspace's dedicated IP, signed with the workspace's DKIM key, and uses the workspace's domain in the From header.
Request body
{
"to": "you@example.com", // string or string[]
"cc": ["x@example.com"], // optional
"bcc": ["audit@acme.com"], // optional
"subject": "hello",
"text": "plain body", // text or html or both required
"html": "<p>html body</p>",
"fromName": "Alice at Acme", // optional, becomes "Alice at Acme <alice@acme.com>"
"replyTo": "alice@acme.com" // optional
}Response · 200
{
"data": {
"messageId": "<…@acme.com>",
"accepted": ["you@example.com"],
"rejected": [],
"response": "250 2.0.0 Ok: queued"
}
}Receiving
/mailboxes/:id/messagesLists recent messages from an IMAP folder. To paginate, pass the nextBefore value from the previous response in the before query parameter.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| folder | string | IMAP folder name. Defaults to "INBOX". |
| limit | 1–100 | Number of messages to return. Defaults to 25. |
| before | uid | Returns messages with a UID less than this value. |
Response · 200
{
"data": {
"folder": "INBOX",
"total": 142,
"messages": [
{
"uid": 142,
"messageId": "<…@example.com>",
"subject": "re: launch",
"from": ["Lena <lena@example.com>"],
"to": ["alice@acme.com"],
"cc": [],
"date": "2026-05-03T08:14:22.000Z",
"flags": ["\\Seen"],
"size": 4821,
"seen": true
}
],
"nextBefore": 117
}
}/mailboxes/:id/messages/:uidReturns a single message: headers, parsed plain-text and HTML bodies, and attachment metadata. Set markSeen=1 to mark the message as read while fetching it.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| folder | string | IMAP folder name. Defaults to "INBOX". |
| markSeen | 0 | 1 | When set to "1", marks the message as read. |
Response · 200
{
"data": {
"uid": 142,
"messageId": "<…@example.com>",
"subject": "re: launch",
"from": "Lena <lena@example.com>",
"to": "alice@acme.com",
"cc": null,
"date": "2026-05-03T08:14:22.000Z",
"text": "saw the launch — congrats…",
"html": "<p>saw the launch — congrats…</p>",
"attachments": [
{ "filename": "deck.pdf", "contentType": "application/pdf", "size": 184221, "contentId": null }
],
"flags": ["\\Seen"],
"seen": true
}
}Versioning
v1 is the current and only version of the API. Breaking changes will be published as /api/v2. v1 will remain available for at least 12 months following the release of a new major version. Non-breaking changes are added to v1 and are announced in the changelog below.
Not available in v1
The following features are planned but not currently available.
- Webhooks for outbound delivery events, bounces, complaints, and IMAP arrivals.
- Scoped keys (read-only, send-only, per-mailbox).
- Bulk send and templating.
- Attachment upload and download endpoints. The /messages/:uid endpoint currently returns attachment metadata only.
Changelog
- 2026-05-03 — v1 released. Endpoints: workspace, domains, IPs, mailboxes, send, list messages, fetch message.
Get an API key
Create a key in the dashboard to start using the API.