openmail-sh Domains API

Custom domains: add your own sending/receiving domain, publish DNS, and verify it

Operations 6

POST /v1/domains Add custom domain #
GET /v1/domains List custom domains #
GET /v1/domains/{id} Get custom domain #
PATCH /v1/domains/{id} Change custom domain pod scope #
DELETE /v1/domains/{id} Delete custom domain #
POST /v1/domains/{id}/verify Re-check custom domain #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/openmail-sh:openmail-sh-domains-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

openmail-sh-domains-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: OpenMail Domains API
  description: Email infrastructure API for AI agents. Create inboxes, send and receive email, and get real-time notifications via webhooks.
  version: 1.0.0
  license:
    name: Proprietary
servers:
- url: https://api.openmail.sh
security:
- bearerAuth: []
tags:
- name: Domains
  description: 'Custom domains: add your own sending/receiving domain, publish DNS, and verify it'
paths:
  /v1/domains:
    post:
      operationId: createDomain
      summary: Add custom domain
      tags:
      - Domains
      description: 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.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
              - domain
              properties:
                domain:
                  type: string
                  minLength: 4
                  maxLength: 253
                  description: The domain to add, e.g. `mail.acme.com`. A subdomain is easiest to set up without affecting your existing mail.
                podId:
                  type:
                  - string
                  - 'null'
                  description: 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.
      responses:
        '201':
          description: Domain added; publish the DNS records to verify it
          content:
            application/json:
              schema:
                description: Domain added; publish the DNS records to verify it
                allOf:
                - $ref: '#/components/schemas/Domain'
        '202':
          description: Domain accepted but under review; DNS setup is blocked until approved. The response includes a `message` explaining why.
          content:
            application/json:
              schema:
                description: Domain accepted but under review; DNS setup is blocked until approved. The response includes a `message` explaining why.
                allOf:
                - $ref: '#/components/schemas/Domain'
        '402':
          description: 'Pro plan: domain creation is paused because the account''s monthly spend limit was reached (`spend_cap_paused`)'
          content:
            application/json:
              schema:
                description: 'Pro plan: domain creation is paused because the account''s monthly spend limit was reached (`spend_cap_paused`)'
                allOf:
                - $ref: '#/components/schemas/Error'
        '403':
          description: Plan does not allow custom domains (`plan_limit`), or the API key is scoped and cannot manage domains (`forbidden`)
          content:
            application/json:
              schema:
                description: Plan does not allow custom domains (`plan_limit`), or the API key is scoped and cannot manage domains (`forbidden`)
                allOf:
                - $ref: '#/components/schemas/Error'
        '409':
          description: You already added this domain (`domain_exists`), it belongs to another account (`domain_taken`), or it needs manual reconnection (`identity_exists`)
          content:
            application/json:
              schema:
                description: You already added this domain (`domain_exists`), it belongs to another account (`domain_taken`), or it needs manual reconnection (`identity_exists`)
                allOf:
                - $ref: '#/components/schemas/Error'
        '422':
          description: Invalid domain (`invalid_domain`) or the domain was flagged as unsafe (`domain_blocked`)
          content:
            application/json:
              schema:
                description: Invalid domain (`invalid_domain`) or the domain was flagged as unsafe (`domain_blocked`)
                allOf:
                - $ref: '#/components/schemas/Error'
        '502':
          description: The mail provider could not set up the domain (`provider_error`)
          content:
            application/json:
              schema:
                description: The mail provider could not set up the domain (`provider_error`)
                allOf:
                - $ref: '#/components/schemas/Error'
    get:
      operationId: listDomains
      summary: List custom domains
      tags:
      - Domains
      description: List the custom domains on your account. A pod-scoped key sees only its pod's domains plus account-wide ones.
      parameters:
      - schema:
          type: integer
          default: 50
        in: query
        name: limit
        required: false
        description: Max results to return (clamped to 100)
      - schema:
          type: integer
          default: 0
        in: query
        name: offset
        required: false
        description: Number of results to skip
      responses:
        '200':
          description: List of custom domains
          content:
            application/json:
              schema:
                description: List of custom domains
                type: object
                additionalProperties: true
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Domain'
                  total:
                    type: integer
        '401':
          description: Missing, invalid, or expired API key
          content:
            application/json:
              schema:
                description: Missing, invalid, or expired API key
                allOf:
                - $ref: '#/components/schemas/Error'
  /v1/domains/{id}:
    get:
      operationId: getDomain
      summary: Get custom domain
      tags:
      - Domains
      description: Retrieve a single custom domain by its ID, including its verification `status` and the DNS records to publish. Poll this after adding a domain to watch it flip to `verified`.
      parameters:
      - schema:
          type: string
        in: path
        name: id
        required: true
      responses:
        '200':
          description: Custom domain details
          content:
            application/json:
              schema:
                description: Custom domain details
                allOf:
                - $ref: '#/components/schemas/Domain'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                description: Not found
                allOf:
                - $ref: '#/components/schemas/Error'
    patch:
      operationId: updateDomain
      summary: Change custom domain pod scope
      tags:
      - Domains
      description: 'Change which pods can create inboxes on a custom domain. Pass `podId: null` to make it account-wide (always allowed), or a pod id to scope it to that pod — allowed only while every inbox on the domain already lives in that pod, since inboxes cannot move between pods. DNS records and verification are untouched. Requires an account-wide API key.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
              - podId
              properties:
                podId:
                  type:
                  - string
                  - 'null'
                  description: Pod to scope the domain to (id or clientId). `null` makes the domain available to all pods.
      parameters:
      - schema:
          type: string
        in: path
        name: id
        required: true
      responses:
        '200':
          description: Scope changed; the returned domain reflects the new `podId`
          content:
            application/json:
              schema:
                description: Scope changed; the returned domain reflects the new `podId`
                allOf:
                - $ref: '#/components/schemas/Domain'
        '400':
          description: Pod not found or not yours (`invalid_pod`)
          content:
            application/json:
              schema:
                description: Pod not found or not yours (`invalid_pod`)
                allOf:
                - $ref: '#/components/schemas/Error'
        '403':
          description: The API key is scoped and cannot change domain scope (`forbidden`)
          content:
            application/json:
              schema:
                description: The API key is scoped and cannot change domain scope (`forbidden`)
                allOf:
                - $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                description: Not found
                allOf:
                - $ref: '#/components/schemas/Error'
        '409':
          description: Inboxes on this domain live outside the target pod (`domain_has_inboxes`); the body includes `outsideCount`
          content:
            application/json:
              schema:
                description: Inboxes on this domain live outside the target pod (`domain_has_inboxes`); the body includes `outsideCount`
                allOf:
                - $ref: '#/components/schemas/Error'
    delete:
      operationId: deleteDomain
      summary: Delete custom domain
      tags:
      - Domains
      description: Remove a custom domain from your account. The domain must have no inboxes — delete those first. This does not remove the DNS records from your registrar. An account-wide key may delete any domain; a pod-scoped key may delete only a domain scoped to its own pod, never an account-wide domain. Inbox-scoped keys cannot manage domains.
      parameters:
      - schema:
          type: string
        in: path
        name: id
        required: true
      responses:
        '204':
          description: Domain deleted
        '403':
          description: The API key is scoped and cannot manage domains (`forbidden`)
          content:
            application/json:
              schema:
                description: The API key is scoped and cannot manage domains (`forbidden`)
                allOf:
                - $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                description: Not found
                allOf:
                - $ref: '#/components/schemas/Error'
        '422':
          description: The domain still has inboxes (`domain_has_inboxes`)
          content:
            application/json:
              schema:
                description: The domain still has inboxes (`domain_has_inboxes`)
                allOf:
                - $ref: '#/components/schemas/Error'
  /v1/domains/{id}/verify:
    post:
      operationId: verifyDomain
      summary: Re-check custom domain
      tags:
      - Domains
      description: Force an immediate DNS re-check of a custom domain, instead of waiting for the automatic background check. The domain verifies on its own within a couple of minutes of you publishing the records, so this is only needed to speed that up. An account-wide key may re-check any domain; a pod-scoped key may re-check only a domain scoped to its own pod, never an account-wide domain. Inbox-scoped keys cannot manage domains.
      parameters:
      - schema:
          type: string
        in: path
        name: id
        required: true
      responses:
        '200':
          description: Re-checked; the returned domain reflects the latest status
          content:
            application/json:
              schema:
                description: Re-checked; the returned domain reflects the latest status
                allOf:
                - $ref: '#/components/schemas/Domain'
        '403':
          description: The API key is scoped and cannot manage domains (`forbidden`)
          content:
            application/json:
              schema:
                description: The API key is scoped and cannot manage domains (`forbidden`)
                allOf:
                - $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                description: Not found
                allOf:
                - $ref: '#/components/schemas/Error'
        '502':
          description: The mail provider check failed (`provider_error`)
          content:
            application/json:
              schema:
                description: The mail provider check failed (`provider_error`)
                allOf:
                - $ref: '#/components/schemas/Error'
components:
  schemas:
    Domain:
      type: object
      additionalProperties: true
      properties:
        id:
          type: string
          description: OpenMail domain ID
        domain:
          type: string
          description: The domain or subdomain, e.g. `example.com` or `agent-mail.example.com`.
        podId:
          type:
          - string
          - 'null'
          description: Pod this domain is scoped to, so only inboxes in that pod can use it, or null for an account-wide domain.
        status:
          type: string
          enum:
          - pending
          - verifying
          - verified
          - failed
          - under_review
          description: '`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:
          type: array
          items:
            $ref: '#/components/schemas/DomainRecord'
          description: 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.
        warnings:
          type: array
          items:
            $ref: '#/components/schemas/DomainWarning'
          description: 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.
        verifiedAt:
          type:
          - string
          - 'null'
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    Error:
      type: object
      additionalProperties: true
      properties:
        error:
          type: string
        message:
          type: string
    DomainRecord:
      type: object
      additionalProperties: true
      description: A single DNS record to publish for a custom domain. Publish every record whose `status` is not already `valid`, then the domain verifies automatically within a couple of minutes.
      properties:
        type:
          type: string
          enum:
          - TXT
          - CNAME
          - MX
        name:
          type: string
          description: The record's host, fully qualified, e.g. `_dmarc.agent-mail.example.com`.
        value:
          type: string
          description: The record value to publish.
        priority:
          type:
          - integer
          - 'null'
          description: MX records only.
        purpose:
          type: string
          enum:
          - spf
          - dkim
          - dmarc
          - mx
          - return-path
          description: '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:
          type: string
          enum:
          - missing
          - invalid
          - valid
          description: 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.
    DomainWarning:
      type: object
      properties:
        code:
          type: string
          enum:
          - competing_mx
          description: '`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:
          type: string
          description: Human-readable explanation and fix, safe to show to the domain owner.
        hosts:
          type: array
          items:
            type: string
          description: The competing MX hosts, lowercase, without trailing dot.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key