# For AI coding agents

> A compact, complete brief for AI coding agents integrating Custom Domain API into a SaaS codebase. Contracts, invariants, and a step-by-step checklist.

Source: https://customdomainapi.com/docs/ai-agents/

Give this page to your coding assistant, or point it at
`https://customdomainapi.com/docs/ai-agents.md`. Everything an
integration needs is below, with links to the full pages.

## Goal

Let each workspace (tenant) of a SaaS app serve the app on a hostname its
customer owns, such as `forms.acme.com`, over HTTPS.

## Facts

- API base URL: `https://edge.customdomainapi.com/v1` on the Free plan,
  otherwise `https://<edge name>/v1`. Auth: `Authorization: Bearer <API key>`.
- OpenAPI: `https://edge.customdomainapi.com/v1/openapi.json`.
- Python SDK: `pip install custom-domain-sdk` (`custom_domain.Client`,
  `CustomDomainMiddleware`, `WorkspaceResolver`, `verify_webhook`).
- Customers CNAME their hostname to `edge.customdomainapi.com` on Free.
- Secrets the app needs (from app.customdomainapi.com, each shown once):
  `CUSTOM_DOMAIN_API_KEY`, `CUSTOM_DOMAIN_ASSERTION_SECRET` with its key id
  (`app_…`), `CUSTOM_DOMAIN_APPLICATION_ID`, and a webhook secret if
  webhooks are used. Read them from the environment; never commit them.

## Invariants (do not break)

1. **Never select the tenant from the `Host` header.** Select it from the
   verified `X-Custom-Domain-Assertion` (`ref` field) only. See
   [Verifying requests](/docs/verify-requests/).
2. Verify the assertion before trusting it: HMAC-SHA256, key looked up by
   id, constant-time compare, `exp`/`iat` with 30 s skew, `app` equals the
   application id, `host` equals the request host.
3. Serve `GET /.well-known/custom-domain-workspace` returning
   `{"reference": "<ref from the verified assertion>"}` (404 if the
   workspace doesn't exist). The domain can't go live without it.
4. Store the domain `id` with the workspace; show the customer the
   `dns_records` exactly as returned.
5. Send `Idempotency-Key` on `POST /v1/domains` so retries are safe.
6. Treat webhooks as at-least-once and unordered: verify the signature,
   dedupe on event `id`, compare `created_at`.
7. Handle `409 hostname_already_claimed`, `409 domain_limit_reached`,
   `422 apex_not_supported` and the other 422 hostname codes with clear
   messages to the customer; respect `429` `Retry-After`.

## Checklist

1. Data model: add to the workspace (or a new table) `custom_hostname`,
   `custom_domain_id`, `custom_domain_status`.
2. Settings UI: a field for the hostname. On save, call `POST /v1/domains`
   with `hostname` and `reference` = the workspace id; store `id` and
   `status`; render the two `dns_records` with copy buttons.
3. Status: show `status` and each failing check's `message`. A "check
   again" button calls `POST /v1/domains/{id}/checks`.
4. Updates: subscribe to `domain.ready`, `domain.attention_required`,
   `domain.recovered` and `domain.deleted` (`POST /v1/webhooks`), and
   update the stored status in the handler; or poll `GET /v1/domains/{id}`.
5. Removing: `DELETE /v1/domains/{id}` when the customer removes the
   hostname or the workspace is deleted.
6. Request routing: add the assertion verification middleware; map `ref` to
   the workspace for every request that carries the header. Requests on the
   app's own domain (no header) keep their existing routing.
7. Absolute URLs: build them from the request's `Host` and
   `X-Forwarded-Proto`; the edge sets both to the customer-facing values.
   Cookies should not set a `Domain` attribute bound to the app's own domain.
8. Tests: a forged or expired assertion is refused; a valid one selects the
   right workspace; `Host` alone never selects a workspace.

## Full pages

[Quickstart](/docs/quickstart.md) · [How it works](/docs/how-it-works.md) ·
[API reference](/docs/api.md) · [Verifying requests](/docs/verify-requests.md) ·
[Webhooks](/docs/webhooks.md) · [Your customers' DNS](/docs/customer-dns.md)
