> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tenantcore.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Automatic DNS

> Plan and queue TenantCore automatic DNS provisioning for domains that already belong to your TenantCore account.

## Overview

The Automatic DNS API operates only on **existing TenantCore domain resources**. You cannot submit an arbitrary domain or Microsoft tenant and ask TenantCore to modify it.

The required ownership chain is:

```plaintext theme={null}
API key
  → TenantCore account UUID
    → TenantCore domain_id
      → parent TenantCore tenant
        → Microsoft / DNS provider operation
```

If the domain does not exist in TenantCore, or its parent tenant belongs to another account, the API returns `404 domain_not_found` before any DNS-provider action is attempted.

<Note>
  Automatic DNS is currently supported through TenantCore's direct Porkbun, Cloudflare, and Namecheap integrations. DNS-provider credentials are connected and managed inside the TenantCore application; this public API does not accept raw registrar credentials.
</Note>

All write requests require an `Idempotency-Key`. Live planning/readiness calls perform external DNS/provider reads and therefore use the public API's **expensive** rate-limit bucket.

***

## Get stored DNS state

Returns TenantCore's most recently stored DNS/authentication state for an owned domain.

```http theme={null}
GET /v1/domains/{domain_id}/dns
```

`domain_id` is the TenantCore domain-row UUID returned by the domain APIs. It is not a domain name.

```bash theme={null}
curl https://api.tenantcore.io/v1/domains/DOMAIN_UUID/dns \
  -H "Authorization: Bearer tc_live_your_key"
```

Example response:

```json theme={null}
{
  "domain_id": "DOMAIN_UUID",
  "tenant_resource_id": "TENANTCORE_TENANT_UUID",
  "tenant_id": "MICROSOFT_TENANT_GUID",
  "domain_name": "mail.example.com",
  "is_default": false,
  "is_verified": true,
  "dkim_enabled": true,
  "dns_status": {
    "mx": "...mail.protection.outlook.com...",
    "spf": "v=spf1 include:spf.protection.outlook.com -all",
    "dmarc": "v=DMARC1; p=quarantine; ...",
    "dkim": "..."
  },
  "verification_dns_records": [],
  "service_configuration_records": [],
  "dkim_dns_records": [],
  "last_checked": "2026-09-27T20:00:00Z"
}
```

This endpoint reads stored state. Use [Build a live DNS plan](#build-a-live-dns-plan) when you need a fresh public-DNS/provider comparison.

***

## Build a live DNS plan

Performs a fresh provider-neutral DNS read and compares the domain with TenantCore's required Microsoft 365 records.

```http theme={null}
GET /v1/domains/{domain_id}/dns/plan
```

```bash theme={null}
curl https://api.tenantcore.io/v1/domains/DOMAIN_UUID/dns/plan \
  -H "Authorization: Bearer tc_live_your_key"
```

The response includes:

* detected authoritative DNS provider
* desired records
* observed records
* create/update/preserve/conflict actions
* whether the DNS read is reliable
* blocking changes that prevent automatic completion

This is a read-only operation. It does not change DNS.

***

## Check automatic-DNS readiness

Checks whether TenantCore can safely apply the current plan through the connected provider.

```http theme={null}
GET /v1/domains/{domain_id}/dns/automation
```

```bash theme={null}
curl https://api.tenantcore.io/v1/domains/DOMAIN_UUID/dns/automation \
  -H "Authorization: Bearer tc_live_your_key"
```

The `automation` object tells you whether:

* the provider is supported for direct API automation
* the account has the required provider connection
* the current changes are safe to apply
* manual action or conflict resolution is required first

A provider connection is always resolved from the authenticated TenantCore account. It cannot be supplied or borrowed from another account through this endpoint.

***

## Start automatic provisioning

Queues TenantCore's persistent DNS provisioning workflow for one owned domain.

```http theme={null}
POST /v1/domains/{domain_id}/dns/provision
```

```bash theme={null}
curl -X POST \
  https://api.tenantcore.io/v1/domains/DOMAIN_UUID/dns/provision \
  -H "Authorization: Bearer tc_live_your_key" \
  -H "Idempotency-Key: dns-provision-mail-example-001"
```

Successful requests return `202 Accepted`.

```json theme={null}
{
  "domain_id": "DOMAIN_UUID",
  "run": {
    "id": "RUN_UUID",
    "status": "queued",
    "total_domains": 1,
    "created_at": "2026-09-27T20:00:00Z",
    "started_at": null,
    "completed_at": null,
    "updated_at": null
  },
  "jobs": [
    {
      "id": "JOB_UUID",
      "run_id": "RUN_UUID",
      "domain_id": "DOMAIN_UUID",
      "tenant_id": "MICROSOFT_TENANT_GUID",
      "domain_name": "mail.example.com",
      "stage": "queued",
      "status": "queued",
      "provider": null,
      "action_required_code": null,
      "message": null,
      "next_attempt_at": "2026-09-27T20:00:00Z"
    }
  ]
}
```

Provisioning continues asynchronously through TenantCore's existing worker. Depending on the current state, the job may move through Microsoft verification, public-DNS propagation, DKIM publication, DKIM enablement, and final health validation.

Only one active automatic provisioning job may exist for a domain at a time. Starting another while one is active returns `409 domain_provisioning_already_active`.

***

## Get a provisioning run

```http theme={null}
GET /v1/dns/provisioning-runs/{run_id}
```

```bash theme={null}
curl https://api.tenantcore.io/v1/dns/provisioning-runs/RUN_UUID \
  -H "Authorization: Bearer tc_live_your_key"
```

The `run_id` must belong to the authenticated TenantCore account. A run from another account is returned as not found.

Possible run states include:

* `queued`
* `running`
* `action_required`
* `completed`
* `failed`

Job responses expose customer-actionable state only. Internal worker leases, account UUIDs, notification IDs, raw provider errors, and internal metadata are not returned by the public API.

***

## List provisioning runs

```http theme={null}
GET /v1/dns/provisioning-runs?active_only=false&limit=20
```

`active_only=true` limits the result to queued/running/action-required work. `limit` may be from `1` to `100`.

***

## Retry a provisioning job

After resolving the condition reported by an `action_required` or waiting job, queue it for another attempt:

```http theme={null}
POST /v1/dns/provisioning-jobs/{job_id}/retry
```

```bash theme={null}
curl -X POST \
  https://api.tenantcore.io/v1/dns/provisioning-jobs/JOB_UUID/retry \
  -H "Authorization: Bearer tc_live_your_key" \
  -H "Idempotency-Key: retry-dns-job-001"
```

The job must belong to a provisioning run owned by the authenticated TenantCore account. TenantCore does not accept a tenant ID, domain name, or provider account in the retry request.

***

## Common errors

| Status | Code                                 | Meaning                                                   |
| ------ | ------------------------------------ | --------------------------------------------------------- |
| `402`  | entitlement error                    | Automatic DNS is not available to the current entitlement |
| `404`  | `domain_not_found`                   | `domain_id` is not an owned TenantCore domain             |
| `404`  | `provisioning_run_not_found`         | Run does not belong to this account                       |
| `404`  | `provisioning_job_not_found`         | Job does not belong to this account                       |
| `409`  | `domain_provisioning_already_active` | An automatic run is already active for the domain         |
| `409`  | provider/plan conflict               | Provider connection or DNS conflict requires attention    |
| `429`  | `rate_limit_exceeded`                | API quota reached; obey `Retry-After`                     |

See [Errors](/api-reference/errors) for the common public API error envelope and request IDs.
