---
title: "Add custom domain"
method: POST
path: "/v1/domains"
tags: ["domains"]
---

# Add custom domain

`POST /v1/domains`

## Request

- Base URL: `https://api.openmail.sh`
- URL: `https://api.openmail.sh/v1/domains`
- Auth: HTTP bearer

Add a custom domain to your account. The response includes the DNS records to publish (`records`). Once you publish them, the domain verifies automatically within a couple of minutes — poll `GET /v1/domains/{id}` until `status` is `verified`, then create inboxes on it. A pod-scoped API key creates the domain scoped to its own pod; inbox-scoped keys cannot manage domains.

## Request body

- object
  - `domain` string, required: The domain to add, e.g. `mail.acme.com`. A subdomain is easiest to set up without affecting your existing mail.
  - `podId` string, nullable: Pod to scope the domain to. Omit or null for an account-wide domain usable by any pod. A pod-scoped API key may only pass its own pod.

## Response `201`

Domain added; publish the DNS records to verify it

- Domain
  - `id` string: OpenMail domain ID
  - `domain` string: The domain or subdomain, e.g. `example.com` or `agent-mail.example.com`.
  - `podId` string, nullable: Pod this domain is scoped to, so only inboxes in that pod can use it, or null for an account-wide domain.
  - `status` 'pending' | 'verifying' | 'verified' | 'failed' | 'under_review': `pending` — added, records not yet published. `verifying` — being checked. `verified` — sending and receiving enabled. `failed` — records missing or wrong; publishing them and calling verify again is the fix. `under_review` — flagged as suspicious; DNS setup is blocked until approved.
  - `records` DomainRecord[]: The DNS records to publish for this domain. Returned even where the values are the same for every customer, so sending infrastructure can change without breaking clients.
    - `type` 'TXT' | 'CNAME' | 'MX'
    - `name` string: The record's host, fully qualified, e.g. `_dmarc.agent-mail.example.com`.
    - `value` string: The record value to publish.
    - `priority` integer, nullable: MX records only.
    - `purpose` 'spf' | 'dkim' | 'dmarc' | 'mx' | 'return-path': What the record is for. Act on this rather than inferring from record shape: SPF must be merged into any the owner already has rather than replacing it, a DMARC policy at an apex can override the owner's own, and an apex MX takes over all their inbound mail.
    - `status` 'missing' | 'invalid' | 'valid': Whether this individual record is published and correct as of the last check. `missing` — not found yet. `invalid` — found but wrong. `valid` — published and correct. Shows which records are still outstanding, not only whether the whole domain passed.
  - `warnings` DomainWarning[]: Problems detected in the domain's DNS that do not block verification but do lose mail. Empty when none. Check this even when `status` is `verified` — every record can be correct and the zone still broken by something else in it.
    - `code` 'competing_mx': `competing_mx` — another MX record is published at the same or a more preferred priority than ours, so senders split the domain's inbound mail between us and a server that does not know its inboxes. Part of the mail bounces `550` while every record shows `valid`. Fix: remove the other MX, or give it a higher priority number than ours.
    - `message` string: Human-readable explanation and fix, safe to show to the domain owner.
    - `hosts` string[]: The competing MX hosts, lowercase, without trailing dot.
  - `verifiedAt` string, date-time, nullable
  - `createdAt` string, date-time
  - `updatedAt` string, date-time

## Other responses

- `202`: Domain accepted but under review; DNS setup is blocked until approved. The response includes a `message` explaining why.
- `402`: Pro plan: domain creation is paused because the account's monthly spend limit was reached (`spend_cap_paused`)
- `403`: Plan does not allow custom domains (`plan_limit`), or the API key is scoped and cannot manage domains (`forbidden`)
- `409`: You already added this domain (`domain_exists`), it belongs to another account (`domain_taken`), or it needs manual reconnection (`identity_exists`)
- `422`: Invalid domain (`invalid_domain`) or the domain was flagged as unsafe (`domain_blocked`)
- `502`: The mail provider could not set up the domain (`provider_error`)

---

[API](https://skmtc.dev/openmail/apis/openmail-api.md) · [All operations](https://skmtc.dev/openmail/apis/openmail-api/llms.txt) · [OpenAPI document](https://skmtc.dev/openmail/apis/openmail-api/revisions/b943eecca7e2?raw)
