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.

  1. Create an API key at /dashboard/api-keys. The plaintext is shown once; only its hash is stored.
  2. Retrieve the mailbox ID with GET /mailboxes.
  3. Submit the message with POST /mailboxes/<id>/send.
cURL
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.

Header
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.

StatusCodeDescription
400validation_errorRequest body or parameters failed validation.
401unauthorizedMissing, malformed, or revoked Bearer token.
402payment_requiredWorkspace has no saved payment method.
404not_foundThe resource does not exist on this workspace.
409conflictUniqueness or state precondition failed.
429rate_limitedRate limit exceeded. See the Retry-After header.
502smtp_errorThe tenant SMTP server rejected the message.
502imap_errorThe tenant IMAP server rejected the connection or fetch.
503inventory_unavailableNo free dedicated IPs available.
500internal_errorUnexpected 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.

BucketRateApplies to
default60 req/min, burst 60All CRUD endpoints.
send30 req/min, burst 10POST /mailboxes/:id/send.
fetch30 req/min, burst 30GET /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

GET/me

Returns 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

GET/domains

Returns 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"
    }
  ]
}
POST/domains

Adds 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"
  }
}
DELETE/domains/:id

Permanently deletes the domain and any mailboxes belonging to it. The Stripe subscription quantity is adjusted automatically.

Response · 200

{ "data": { "deletedMailboxes": 2 } }

Dedicated IPs

GET/ips

Returns 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
    }
  ]
}
POST/ips

Orders additional dedicated IPs. Requires at least one domain on the workspace.

Request body

{ "count": 1 }   // 1–10

Response · 200

{
  "data": {
    "orderId": "ord_…",
    "status": "pending_provision",
    "ips": 1
  }
}
DELETE/ips/:id

Releases 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

GET/mailboxes

Returns 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"
    }
  ]
}
POST/mailboxes

Creates 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"]
  }
}
GET/mailboxes/:id

Returns 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"
  }
}
DELETE/mailboxes/:id

Permanently deletes a mailbox. Removes it from the Dovecot password file and adjusts the Stripe subscription.

Response · 200

{ "data": { "deleted": "alice@acme.com" } }

Sending

POST/mailboxes/:id/send

Submits 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

GET/mailboxes/:id/messages

Lists recent messages from an IMAP folder. To paginate, pass the nextBefore value from the previous response in the before query parameter.

Query parameters

ParameterTypeDescription
folderstringIMAP folder name. Defaults to "INBOX".
limit1–100Number of messages to return. Defaults to 25.
beforeuidReturns 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
  }
}
GET/mailboxes/:id/messages/:uid

Returns 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

ParameterTypeDescription
folderstringIMAP folder name. Defaults to "INBOX".
markSeen0 | 1When 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.

Open dashboard