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

# Custom domains

> Serve every URL your workspace has under your own domain, by delegating a dedicated domain's nameservers.

A **custom domain** is a workspace-level mirror of everything you serve on `agent37.app`. Register `acme-agents.com` once, and every URL your workspace has also answers under it: instance `ab12cd34ef` becomes reachable at `https://ab12cd34ef.acme-agents.com` with the same `X-Agent37-Key` and signed-URL authentication as its [instance URL](/agents-api/urls), preview URLs work at `https://ab12cd34ef-8080.acme-agents.com`, and every [public port](/agents-api/public-ports) hostname serves credential-free at `https://{hostname}.acme-agents.com`. Your `agent37.app` URLs keep working unchanged; the custom domain is an addition, not a replacement.

Use a **dedicated domain** registered for this purpose, like `acme-agents.com` or `acme.sh`, not your main website's domain: you delegate the whole domain's DNS to us, and we host it from then on. One wildcard TLS certificate then covers your entire fleet, whether that's one instance or ten thousand, with nothing to provision per instance. Subdomains of an existing domain (`previews.acme.com`) cannot be registered.

Setup is three steps: register, set the nameservers at your registrar, verify.

## Register the domain

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.agent37.com/v1/domains \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{ "domain": "acme-agents.com" }'
  ```

  ```python python theme={null}
  import requests

  r = requests.post(
      "https://api.agent37.com/v1/domains",
      headers={"Authorization": "Bearer sk_live_..."},
      json={"domain": "acme-agents.com"},
  )
  print(r.json()["dns"])
  ```

  ```javascript node theme={null}
  const res = await fetch("https://api.agent37.com/v1/domains", {
    method: "POST",
    headers: { Authorization: "Bearer sk_live_...", "Content-Type": "application/json" },
    body: JSON.stringify({ domain: "acme-agents.com" }),
  });
  console.log((await res.json()).dns);
  ```

  ```json response theme={null}
  {
    "domain": "acme-agents.com",
    "status": "pending",
    "dns": {
      "type": "NS",
      "name": "acme-agents.com",
      "nameservers": ["ada.ns.cloudflare.com", "rob.ns.cloudflare.com"]
    },
    "created": 1751928000
  }
  ```
</CodeGroup>

<ResponseField name="domain" type="string">
  The domain you registered, lowercased. What your workspace serves lives one label below it (`ab12cd34ef.acme-agents.com`).
</ResponseField>

<ResponseField name="status" type="string">
  `pending` until verification passes, then `active`. Only an active domain routes.
</ResponseField>

<ResponseField name="dns" type="object">
  The nameservers to set at your domain's registrar: replace the domain's existing nameservers with the two in `dns.nameservers`.
</ResponseField>

<ResponseField name="created" type="integer">
  Unix seconds when the domain was registered.
</ResponseField>

Custom domains are a top-tier feature: they unlock once your workspace's top-ups total \$500, the same tier that raises the instance limit to 50. Below it, registration returns `403 tier_limit`; email [vishnu@agent37.com](mailto:vishnu@agent37.com) to unlock them another way.

Register the domain itself, `acme-agents.com`, not `*.acme-agents.com`: the wildcard is implied. The name must be a registrable domain you own; a subdomain of another domain returns `400 invalid_request`. A domain is claimable by exactly one workspace across the platform, first come, so a name another workspace holds returns `409 domain_exists`. A registration can only ever verify with control of the domain at its registrar, so squatting buys nothing; if a domain you own is already taken, email [vishnu@agent37.com](mailto:vishnu@agent37.com).

## Set the nameservers

At the registrar where the domain is registered, replace its nameservers with the two from the response's `dns.nameservers`. That is the whole DNS setup: no individual records to manage, we host the domain's DNS from then on. Registrar nameserver changes usually land within minutes, occasionally hours.

## Verify

Once the nameservers are set, verify:

```bash curl theme={null}
curl -X POST https://api.agent37.com/v1/domains/acme-agents.com/verify \
  -H "Authorization: Bearer sk_live_..."
```

```json response theme={null}
{ "domain": "acme-agents.com", "status": "active" }
```

Verification checks that the delegation has landed. It is idempotent and safe to poll: call it right after changing the nameservers, and again if it still answers `pending` (each call also nudges the delegation re-check, so polling genuinely speeds things up). The moment the status flips to `active`, the domain starts routing.

TLS is one wildcard certificate covering every hostname under the domain, issued automatically when the domain activates and renewed forever after. A TLS error in the first minutes after activation means the certificate has not landed yet; wait and retry. There is nothing to provision per instance, at any fleet size.

## Use it

Hit the mirrored instance URL exactly as you would the `agent37.app` one:

```bash curl theme={null}
curl https://ab12cd34ef.acme-agents.com/v1/health \
  -H "X-Agent37-Key: sk_live_..."
```

```json response theme={null}
{ "ok": true, "agent": "hermes", "healthy": true, "hermes": true }
```

Authentication is identical to `agent37.app`: instance URLs and preview URLs (`ab12cd34ef-8080.acme-agents.com`) take an `X-Agent37-Key` header or a signed URL, public-port hostnames need no credential at all. A [signed URL](/agents-api/urls#browser-access-with-signed-urls)'s token is bound to the label it was minted for, not the domain, so a token minted for the default port also opens `https://ab12cd34ef.acme-agents.com/?a37_token=...`.

Only labels your own workspace serves resolve under your domain. Any other label answers exactly as an unknown `agent37.app` URL would (`401` without a credential, `404` with one), so nothing another workspace runs can ever appear under your domain.

Once a domain is active, instance reads expose the mirrored URLs as `domain_urls`, on the instance, on each public-port entry, and on minted [signed URLs](/agents-api/urls#browser-access-with-signed-urls). Entries are ordered oldest domain first, and the dashboard's open and copy-link actions lead with that first domain:

```json theme={null}
{
  "id": "ab12cd34ef",
  "url": "https://ab12cd34ef.agent37.app",
  "domain_urls": ["https://ab12cd34ef.acme-agents.com"],
  "public_ports": [
    {
      "port": 8644,
      "url": "https://a1b2c3d4e5f6a7b8c9d0.agent37.app",
      "domain_urls": ["https://a1b2c3d4e5f6a7b8c9d0.acme-agents.com"],
      "prefix": null,
      "created": 1751928000
    }
  ]
}
```

## List and delete

`GET /v1/domains` lists the workspace's domains as `{ "data": [...] }`, each entry in the full shape above (including the `dns` block, so you can always re-read the nameservers to set). `GET /v1/domains/{domain}` reads one.

```bash curl theme={null}
curl -X DELETE https://api.agent37.com/v1/domains/acme-agents.com \
  -H "Authorization: Bearer sk_live_..."
```

```json response theme={null}
{ "domain": "acme-agents.com", "deleted": true }
```

Deleting stops routing under the domain within seconds and releases its DNS hosting; your `agent37.app` URLs are unaffected. The delete acts once; repeating it returns `404`.

## Rules and limits

* **Top tier only.** Registering a domain requires total top-ups of \$500; below that, `POST /v1/domains` returns `403 tier_limit`.
* **Up to 10 domains per workspace**, at no charge. Registering an eleventh returns `400 invalid_request`; a bad hostname or a domain under `agent37.app` or `agent37.com` (reserved) returns `400` too.
* **Dedicated domains only.** The whole domain's DNS is delegated to us, so it cannot carry your website, mail, or other records. Register a domain just for this.
* The path `/health` is answered by the platform edge itself on custom domains too, just as on `agent37.app`, and never reaches your app.
* Traffic on a custom domain is ordinary instance traffic: same billing, and a request [wakes a sleeping instance](/agents-api/urls#sleeping-instances-wake-on-request) the same way.
* Reads, verifies, and deletes are uniform: a domain registered by another workspace returns the same `404` as one that does not exist.
