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

# Domains

> Org-level sending and receiving domains, their DNS records, and the three readiness tracks.

<Info>Domains are part of [Identity](/docs/identity/overview). A domain belongs to the **organization**, not to one persona — two identities can send from the same domain.</Info>

A domain is what makes an [email inbox](/docs/api/messaging) deliverable. Connect a domain you own (`external`) or mint the organization's system subdomain (`system`), then publish the DNS records the API hands back.

## Three readiness tracks

Verification is not one boolean. A domain advances along three independent tracks, and each gates a different capability:

| Field                | Gates                       | Values                                          |
| -------------------- | --------------------------- | ----------------------------------------------- |
| `dns_status`         | **Sending** mail (SPF/DKIM) | `pending_verification`, `provisioned`, `failed` |
| `inbound_status`     | **Receiving** mail (MX)     | `pending`, `verified`, `failed`                 |
| `app_connect_status` | HTTP/app hosting            | `pending`, `connected`, `failed`                |

A send is refused with a retryable `job_not_ready` until `dns_status` is `provisioned`. Receiving is unaffected by `dns_status` and waits on `inbound_status`.

## The domain object

<ResponseField name="id" type="string">Unique id (e.g. `dom_01H...`).</ResponseField>
<ResponseField name="object" type="string">Always `domain`.</ResponseField>
<ResponseField name="name" type="string">The fully-qualified name, lowercase, no trailing dot.</ResponseField>
<ResponseField name="kind" type="string">`external` (a domain you own) or `system` (a subdomain minted for the org).</ResponseField>
<ResponseField name="status" type="string">Overall lifecycle state of the domain record — `pending_dns`, `active`, or `failed`.</ResponseField>
<ResponseField name="dns_status" type="string">Mail-sending track. See the table above.</ResponseField>
<ResponseField name="inbound_status" type="string">Mail-receiving track.</ResponseField>
<ResponseField name="app_connect_status" type="string">App/HTTP track.</ResponseField>
<ResponseField name="records" type="object[]">The DNS records to publish. Same shape as [List DNS records](#list-dns-records).</ResponseField>
<ResponseField name="last_verified_at" type="string | null">When verification last ran.</ResponseField>
<ResponseField name="created_at" type="string">Creation timestamp.</ResponseField>

## Connect a domain

`POST /v1/domains` — scope `agents:write`

Idempotent on `name`: connecting a domain that already exists returns the existing record rather than a duplicate.

<ParamField body="kind" type="string">`external` (a domain you own), `system` (a subdomain minted under the org slug), or `purchased`. Defaults to `external`. `purchased` is accepted by the schema but answers `501 feature_not_configured` — there is no outbound payment path to a registrar.</ParamField>

<ParamField body="name" type="string">
  The domain to connect. Required for `kind: "external"`; omit for `system` and the API mints the subdomain.
</ParamField>

<ParamField body="receive" type="boolean">Also request inbound (MX) records. Defaults to `true`.</ParamField>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -fsSL https://api.vetta.sh/v1/domains \
    -H "authorization: Bearer sk_live_..." \
    -H "content-type: application/json" \
    -d '{ "kind": "external", "name": "mail.acme.dev", "receive": true }'
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={"system"}
  {
    "id": "dom_01H9DM...",
    "object": "domain",
    "name": "mail.acme.dev",
    "kind": "external",
    "status": "pending_dns",
    "dns_status": "pending_verification",
    "inbound_status": "pending",
    "app_connect_status": "pending",
    "records": [
      { "type": "TXT", "host": "@", "value": "v=spf1 include:...", "priority": null, "managed": true }
    ],
    "last_verified_at": null,
    "created_at": "2026-08-20T17:00:00Z"
  }
  ```
</ResponseExample>

**Errors** — `validation_failed` (400) when `name` is not a valid FQDN or `kind` is unknown; `insufficient_scope` (403) without `agents:write`.

## List domains

`GET /v1/domains` — scope `agents:read`

<ParamField query="limit" type="integer">Page size. See [Pagination](/docs/api/pagination).</ParamField>
<ParamField query="after" type="string">Cursor from the previous page.</ParamField>

Returns a [paginated list](/docs/api/pagination) of domain objects.

## Retrieve a domain

`GET /v1/domains/{id}` — scope `agents:read`

Returns the domain object. `not_found` (404) if the id does not belong to your organization.

## Verify a domain

`POST /v1/domains/{id}/verify` — scope `agents:write`

Re-runnable. Each readiness track advances on its own, so calling `verify` after publishing only the SPF/DKIM records moves `dns_status` and leaves `inbound_status` where it was. The **domain object** comes back as the answer — read the three status fields to see what changed.

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -fsSL -X POST https://api.vetta.sh/v1/domains/dom_01H9DM.../verify \
    -H "authorization: Bearer sk_live_..."
  ```
</CodeGroup>

**Errors** — `not_found` (404); `insufficient_scope` (403). A track that is still unpublished is not an error: it simply stays `pending`.

## List DNS records

`GET /v1/domains/{id}/records` — scope `agents:read`

Always allowed on every `kind` — reading the zone is never the dangerous half.

<ResponseField name="type" type="string">`TXT`, `CNAME`, or `MX`.</ResponseField>
<ResponseField name="host" type="string">The record host, relative to the registrable zone.</ResponseField>
<ResponseField name="value" type="string">The record value.</ResponseField>
<ResponseField name="priority" type="integer | null">MX priority; `null` for `TXT` and `CNAME`.</ResponseField>

<ResponseField name="managed" type="boolean">
  `true` for records the platform owns — DKIM, `_dmarc`, and the inbound MX. An agent may read a managed record and must never edit or delete it: removing the DKIM record breaks the signature on its own mail.
</ResponseField>

<ResponseExample>
  ```json Response theme={"system"}
  {
    "object": "list",
    "data": [
      { "type": "MX",    "host": "@",                "value": "inbound.vetta.sh", "priority": 10,   "managed": true },
      { "type": "TXT",   "host": "_dmarc",           "value": "v=DMARC1; p=none", "priority": null, "managed": true },
      { "type": "CNAME", "host": "sel._domainkey",   "value": "sel.dkim...",      "priority": null, "managed": true }
    ]
  }
  ```
</ResponseExample>

## Delete a domain

`DELETE /v1/domains/{id}` — scope `agents:write`

Returns the standard deleted envelope. Inboxes on the domain stop delivering; released addresses are not reusable until the domain is reconnected and re-verified.

<ResponseExample>
  ```json Response theme={"system"}
  { "id": "dom_01H9DM...", "object": "domain", "deleted": true }
  ```
</ResponseExample>

<Card title="Email & SMS" icon="arrow-right" href="/docs/api/messaging">
  Give a persona an inbox on this domain.
</Card>
