---
title: "Set up the DNS of a domain"
description: "Whether OpenEmail writes the records of a domain itself, through which connected zone, and where each kind of record stands."
url: "https://openemail.uk/docs/api/domains/dns"
area: "API"
category: "Mailbox"
---

# Set up the DNS of a domain

Whether OpenEmail writes the records of a domain itself, through which connected zone, and where each kind of record stands.

`GET /domains/{id}/dns`

**Also documents:** `PUT /domains/{id}/dns`, `POST /domains/{id}/dns/sync`

## GET /domains/{id}/dns

Whether OpenEmail writes the records of a domain itself, through which connected zone, and where each kind of record stands.

## Read the setup

Needs `domains:read`. `managing` is true while OpenEmail writes the records itself. `zone` says which connected zone answers for the domain: one is `resolved`, several are `ambiguous` and need a choice, `none` holds it, or the connections could not be asked (`unusable`). Add `?refresh=true` to ask the providers again.

**curl**

```
curl "$OE/domains/b3e1f0a4-6c2d-4e8a-9f17-2d5c8a0b4e6f/dns" -H "$AUTH"
```

**Response**

```
{
  "object": "domain_dns",
  "domainId": "b3e1f0a4-6c2d-4e8a-9f17-2d5c8a0b4e6f",
  "domain": "acme.com",
  "managing": true,
  "connectionId": "dnsl_3f9a1c2e7b4d4e6f8a0b2c3d",
  "connection": {
    "id": "dnsl_3f9a1c2e7b4d4e6f8a0b2c3d",
    "status": "active",
    "subject": "ana@acme.com",
    "accounts": [{ "id": "4f1c2b3a5d6e7f8091a2b3c4d5e6f708", "name": "Acme" }]
  },
  "zoneId": "023e105f4ecef8ad9ca31a8372d0c353",
  "zoneName": "acme.com",
  "zoneHolder": "Acme",
  "state": "ready",
  "steps": [
    { "purpose": "mx", "ok": true, "detail": "MX records are in place.", "visibility": "public", "records": [] }
  ],
  "leftovers": [],
  "probe": null,
  "error": null,
  "syncedAt": "2026-09-30T08:01:12.000Z",
  "zone": {
    "kind": "resolved",
    "checkedAt": "2026-10-01T09:00:00.000Z",
    "cached": true,
    "candidate": {
      "connectionId": "dnsl_3f9a1c2e7b4d4e6f8a0b2c3d",
      "subject": "ana@acme.com",
      "status": "active",
      "accounts": [{ "id": "4f1c2b3a5d6e7f8091a2b3c4d5e6f708", "name": "Acme" }],
      "account": { "id": "4f1c2b3a5d6e7f8091a2b3c4d5e6f708", "name": "Acme" },
      "zone": {
        "id": "023e105f4ecef8ad9ca31a8372d0c353",
        "name": "acme.com",
        "accountId": "4f1c2b3a5d6e7f8091a2b3c4d5e6f708",
        "active": true,
        "status": "active",
        "type": "full",
        "nameServers": ["ana.ns.cloudflare.com", "bob.ns.cloudflare.com"],
        "covers": true
      }
    },
    "candidates": [],
    "reason": null,
    "connected": null,
    "host": null,
    "blocked": []
  }
}
```

> `leftovers` lists records OpenEmail wrote and could not take down, to delete by hand at the provider.

## Choose the zone

Needs `domains:write`. `PUT /domains/{id}/dns` with `{ connectionId, zoneId }` attaches the domain to a zone when several could answer for it. The zone has to cover the domain, be active and take a test record. Nothing is written yet.

**curl**

```
curl -X PUT "$OE/domains/b3e1f0a4-6c2d-4e8a-9f17-2d5c8a0b4e6f/dns" -H "$AUTH" \
  -H "Content-Type: application/json" \
  -d '{ "connectionId": "dnsl_3f9a1c2e7b4d4e6f8a0b2c3d", "zoneId": "023e105f4ecef8ad9ca31a8372d0c353" }'
```

**Response**

```
{
  "object": "domain_dns",
  "domainId": "b3e1f0a4-6c2d-4e8a-9f17-2d5c8a0b4e6f",
  "domain": "acme.com",
  "managing": true,
  "connectionId": "dnsl_3f9a1c2e7b4d4e6f8a0b2c3d",
  "connection": {
    "id": "dnsl_3f9a1c2e7b4d4e6f8a0b2c3d",
    "status": "active",
    "subject": "ana@acme.com",
    "accounts": [{ "id": "4f1c2b3a5d6e7f8091a2b3c4d5e6f708", "name": "Acme" }]
  },
  "zoneId": "023e105f4ecef8ad9ca31a8372d0c353",
  "zoneName": "acme.com",
  "zoneHolder": "Acme",
  "state": "awaiting-sync",
  "steps": [
    { "purpose": "mx", "ok": true, "detail": "MX records are in place.", "visibility": "public", "records": [] }
  ],
  "leftovers": [],
  "probe": null,
  "error": null,
  "syncedAt": "2026-09-30T08:01:12.000Z",
  "zone": {
    "kind": "resolved",
    "checkedAt": "2026-10-01T09:00:00.000Z",
    "cached": true,
    "candidate": {
      "connectionId": "dnsl_3f9a1c2e7b4d4e6f8a0b2c3d",
      "subject": "ana@acme.com",
      "status": "active",
      "accounts": [{ "id": "4f1c2b3a5d6e7f8091a2b3c4d5e6f708", "name": "Acme" }],
      "account": { "id": "4f1c2b3a5d6e7f8091a2b3c4d5e6f708", "name": "Acme" },
      "zone": {
        "id": "023e105f4ecef8ad9ca31a8372d0c353",
        "name": "acme.com",
        "accountId": "4f1c2b3a5d6e7f8091a2b3c4d5e6f708",
        "active": true,
        "status": "active",
        "type": "full",
        "nameServers": ["ana.ns.cloudflare.com", "bob.ns.cloudflare.com"],
        "covers": true
      }
    },
    "candidates": [],
    "reason": null,
    "connected": null,
    "host": null,
    "blocked": []
  }
}
```

> A zone that does not cover the domain is 422 `invalid_parameter` on `zoneId`. A domain set up through another zone is 409 `dns_zone_conflict`, and a zone that is not active or refuses the test record is 409 `dns_zone_unusable`.

> An OAuth access token needs a verification code for this call. Until the app has verified one in the last 60 minutes, the call answers `403` `step_up_required` and changes nothing. An API key is never asked. The Authentication page shows how to ask for a code and verify it.

## Write the records

Needs `domains:write`. `POST /domains/{id}/dns/sync` writes or repairs every record the domain needs, as Sync does in the app, and attaches the zone first when only one answers. Send `{ "purpose": "dmarc" }` to write only one kind of record.

**curl**

```
curl -X POST "$OE/domains/b3e1f0a4-6c2d-4e8a-9f17-2d5c8a0b4e6f/dns/sync" -H "$AUTH"
```

**Response**

```
{
  "object": "domain_dns_sync",
  "outcome": "synced",
  "message": null,
  "attached": false,
  "provision": {
    "outcome": "applied",
    "state": "ready",
    "connectionId": "dnsl_3f9a1c2e7b4d4e6f8a0b2c3d",
    "zoneId": "023e105f4ecef8ad9ca31a8372d0c353",
    "proven": true,
    "ownership": "The ownership record is in place.",
    "written": [{ "purpose": "dmarc", "name": "_dmarc.acme.com", "type": "TXT", "mode": "created" }],
    "adopted": 6,
    "conflicts": [],
    "failures": [],
    "steps": [],
    "retired": [],
    "leftovers": [],
    "probe": null,
    "error": null
  },
  "dns": { "object": "domain_dns", "domain": "acme.com", "managing": true, "state": "ready", "…": "…" },
  "zone": { "kind": "resolved", "cached": false, "…": "…" }
}
```

> When no single zone answers, nothing is written: `outcome` is `refused`, `message` says why, and `zone` lists the zones to choose from.

> A sync already running on the domain is 409 `dns_busy`, and a provider that refuses the request is 502 `dns_provider_error`. A domain waiting for its records is verified once they are in place.

> An OAuth access token needs a verification code for this call. Until the app has verified one in the last 60 minutes, the call answers `403` `step_up_required` and changes nothing. An API key is never asked. The Authentication page shows how to ask for a code and verify it.

## Reference

- [`GET /domains/{id}/dns`](https://openemail.uk/docs/api/reference/domains#get-domains-id-dns): full reference
- [`PUT /domains/{id}/dns`](https://openemail.uk/docs/api/reference/domains#put-domains-id-dns): full reference
- [`POST /domains/{id}/dns/sync`](https://openemail.uk/docs/api/reference/domains#post-domains-id-dns-sync): full reference
