Apply DNS with a customer token
POST /v1/domains/{id}/dns/apply — point a domain using a scoped API token the customer supplies.
POST /v1/domains/{id}/dns/apply
Writes the DNS records for you, using a scoped API token your customer supplies for their own DNS provider.
This is the third of three ways to get a domain pointed, and it exists for the gap between the other two. Connect instructions tell the customer exactly what to type. Domain Connect removes the typing where the provider supports it. This covers the technical customer on Cloudflare or DigitalOcean, who would rather paste a scoped token than click through a DNS panel and whose provider will never be reachable through Domain Connect.
Supported providers
provider | Token needs |
|---|---|
cloudflare | Zone:Zone:Read and Zone:DNS:Edit, scoped to the one zone |
digitalocean | write scope on Domains |
Anything else returns unsupported_provider. Use the connect instructions
instead.
The token
Send it in the body, never the query string. Request URLs end up in access logs; bodies do not.
Domainee uses the token for the duration of the request and then discards it. It is never stored, never logged, and never returned in a response. There is nothing to revoke afterwards and nothing for you to rotate.
Tell your customers to scope the token to the single zone being connected. Neither provider needs account-wide access for this.
Request
curl -X POST https://api.domainee.dev/v1/domains/$DOMAIN_ID/dns/apply \
-H "Authorization: Bearer $DOMAINEE_API_KEY" \
-H "content-type: application/json" \
-d '{
"provider": "cloudflare",
"token": "<the customer'\''s scoped DNS token>"
}'
Response — 200 OK
{
"applied": {
"provider": "cloudflare",
"written": [
{ "type": "A", "name": "acme.com", "value": "75.2.52.197", "ttl": 300 },
{ "type": "A", "name": "acme.com", "value": "3.33.255.144", "ttl": 300 }
],
"removed": 1
},
"domain": { "...": "the refreshed Domain object" }
}
removed counts conflicting records deleted to make room. The domain is
re-probed immediately, so domain.status is current rather than waiting up to
five minutes for the next scheduled check.
What it touches
Only the records that would conflict at the hostname being pointed: A,
AAAA, CNAME and ALIAS at that exact name. Everything else in the zone is
left alone, including MX, TXT, SPF, DKIM, DMARC and every other
hostname. Your customer's email keeps working.
At a root domain we write the anycast A records rather than a CNAME. A CNAME
at a zone apex is exclusive, so using one there would mean deleting the MX
records sitting beside it.
On Cloudflare, records are created with proxy disabled deliberately. An orange-clouded record makes Cloudflare terminate TLS itself and our certificate is never issued. Writing the record ourselves is what removes that failure mode for good.
Errors
| Code | Status | Meaning |
|---|---|---|
auth_failed | 400 | The provider rejected the token, or it lacks the scopes above. |
zone_not_found | 404 | The provider has no zone for this domain, or the token cannot see it. |
write_failed | 422 | The provider accepted the token but refused a record. Message contains their reason. |
unsupported_provider | 422 | We can't write to that provider. |
no_anycast_configured | 422 | Apex domain on a deployment with no anycast addresses. |
not_found | 404 | Domain doesn't exist in this workspace. |
No MCP tool, on purpose
Unlike the rest of this feature set, this is not exposed to AI agents. Routing
a customer's DNS credentials through an agent's context is a bad trade for the
convenience. Agents get
get_connect_instructions instead,
which needs no secrets at all.