Docs / API reference

API reference

The Custom Domain API v1 reference. Register customer hostnames, read their DNS records and checks, re-check and delete them, with idempotency, pagination, errors and limits.

Base URL: https://edge.customdomainapi.com/v1 on Free; on a paid or self-hosted edge, https://<your edge name>/v1.

Authentication: every request sends your API key:

Authorization: Bearer cd_...

The application is derived from the key, never from a request field. Keys are not accepted in query strings or cookies.

Machine-readable contract: the OpenAPI document is at /v1/openapi.json on every edge, and in the repository as docs/openapi.json.

Endpoints

Method and path Purpose Success
POST /v1/domains Register a hostname for a workspace. 201, or 200 on an idempotent replay
GET /v1/domains List domains. Filters: reference, status, include_deleted. Pages: limit (1 to 200, default 50), offset. 200
GET /v1/domains/{id} One domain with its current checks. 200
POST /v1/domains/{id}/checks Re-run all checks now. 202
DELETE /v1/domains/{id} Stop serving and delete. 202
POST /v1/webhooks Subscribe to events (Webhooks). 201
GET /v1/webhooks List subscriptions. 200
POST /v1/webhooks/{id}/rotate New signing secret. 200
DELETE /v1/webhooks/{id} Revoke a subscription. 200

Register a domain

POST /v1/domains
Authorization: Bearer cd_...
Idempotency-Key: signup-ws_8f3a1c
Content-Type: application/json

{"hostname": "forms.acme.com", "reference": "ws_8f3a1c", "metadata": {"plan": "pro"}}
  • hostname: the customer’s exact subdomain. It’s normalized to lowercase punycode. Apex domains (acme.com), wildcards and IP addresses are refused.
  • reference: your opaque workspace id (up to 255 characters). It is returned verbatim and delivered to your origin in the assertion.
  • metadata: optional, up to 32 scalar values, echoed back.

Response 201:

{
  "id": "6f1c2d3e-4b5a-4c6d-8e9f-0a1b2c3d4e5f",
  "hostname": "forms.acme.com",
  "reference": "ws_8f3a1c",
  "status": "pending_dns",
  "dns_records": [
    {"name": "_custom-domain-challenge.forms.acme.com", "type": "TXT",
     "value": "custom-domain-verify=…", "purpose": "ownership", "help": "…"},
    {"name": "forms.acme.com", "type": "CNAME",
     "value": "edge.customdomainapi.com", "purpose": "routing", "help": "…"}
  ],
  "checks": [
    {"type": "ownership", "status": "pending", "error_code": null, "message": null,
     "observed_at": null, "next_check_at": null},
    {"type": "routing", "status": "pending"},
    {"type": "certificate", "status": "pending"},
    {"type": "origin", "status": "pending"}
  ],
  "metadata": {"plan": "pro"},
  "created_at": "2026-10-04T14:00:00Z",
  "updated_at": "2026-10-04T14:00:00Z",
  "deleted_at": null
}

Show both dns_records to your customer exactly as returned. Statuses and checks are explained in How it works.

Idempotency

Send Idempotency-Key (up to 255 characters, unique per application) on POST /v1/domains, and retries are safe:

  • the same key and body returns the first result with 200 and Idempotent-Replayed: true;
  • the same key with a different body is 422 idempotency_key_reused;
  • a retry while the first is still running is 409 idempotency_request_in_progress;
  • keys expire after 24 hours.

Errors

Every error has the same shape:

{"error": {"code": "hostname_already_claimed", "message": "forms.acme.com is already claimed", "details": {}}}
Status code When
401 unauthorized Missing, unknown, revoked or expired key.
403 application_suspended Your application is suspended.
404 domain_not_found No such domain in your application. Another application’s domains are never visible.
409 hostname_already_claimed The hostname is live in this or another application.
409 domain_limit_reached Your application is at its domain limit (25 on Free). Existing domains keep working.
409 invalid_status_transition Not possible in the domain’s status, such as re-checking a deleted domain.
409 idempotency_request_in_progress The first request with this key hasn’t finished.
422 validation_error The body or query doesn’t match the schema; details.errors lists where.
422 apex_not_supported, wildcard_not_supported, ip_literal_not_supported, invalid_hostname, invalid_label, hostname_too_long, empty_hostname The hostname can’t be registered.
422 invalid_reference The reference is empty or longer than 255 characters.
422 idempotency_key_reused The key was used with a different body.
429 rate_limited Too many requests; wait Retry-After seconds.

Limits on Free

Limit Value
Customer hostnames 25
API keys 1 (two during a rotation’s 24-hour overlap)
API requests 60 a minute per key
Proxied requests 600 a minute and 100 a second, across all your hostnames; over either, visitors get 429 with Retry-After
Manual re-checks 1 per domain a minute, 60 per application an hour
Fair use 10 GB of responses served a month: above it we email you; nothing is switched off

Paid plans have their own edge, with more domains and no proxied-request limit of ours.

SDK

The Python SDK wraps all of the above, with typed errors and safe retries:

pip install custom-domain-sdk

See the SDK README.