Every deed.page site already has a permanent address at https://{slug}.deed.page/. Sometimes a person wants the page on their own name instead, like docs.example.com for project docs an agent keeps current, or example.com for a landing page. deed.page lets the agent attach that hostname with an API call. Nobody has to open a dashboard. The agent gets back the exact DNS records to create, and then polls until the domain is live.
Every response below was captured against production on October 22, 2026. The test site used X-Ttl: 900, the test hostname was removed, and the site was deleted afterwards.
Limits per plan
| Free | Hobby ($3/mo) | Pro ($15/mo) | |
|---|---|---|---|
| Custom hostnames per token | 1 | 3 | 20 |
Domain calls (add, status, remove) are also capped at 60 an hour per token and 60 an hour per network. The cap is there to stop polling loops, and normal use stays well under it.
Attach a hostname
You need a site the token owns. Then one POST with the hostname as JSON attaches it:
curl -sS -X POST "https://deed.page/v1/sites/$SLUG/domains" \
-H "Authorization: Bearer $DEEDPAGE_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"domain":"docs.example.com"}'The answer is 201 with the records to create (values shortened here):
{"ok":true,"domain":"docs.example.com","slug":"my-docs","apex":false,
"status":"pending_dns",
"status_reason":"Create the CNAME and every TXT record. Then check status again.",
"dns_records":[
{"type":"CNAME","name":"docs","host":"docs.example.com",
"value":"9f5677dd5e4f32ed.vercel-dns-016.com","purpose":"routing",
"note":"Create this CNAME. Do not also create an A record for this name."},
{"type":"TXT","name":"_vercel","host":"_vercel.example.com",
"value":"vc-domain-verify=docs.example.com,9b71...","purpose":"verification",
"note":"Ownership verification required by the hosting project. Create this record exactly."}],
"url":"https://docs.example.com/"}Treat dns_records as the source of truth and don't hard-code values. name is relative to the zone, so it is ready to paste into most DNS panels, and host is the fully qualified name. The routing target is specific to each hostname. Here is what the list holds:
- A subdomain (
docs.example.com) gets oneCNAME. - An apex (
example.com,"apex":true) gets anArecord and anALIASrecord. Create one of the two, not both. UseALIAS(sometimes calledANAME) if your DNS host supports it. - TXT records prove ownership. The hosting layer sometimes asks for its own
_vercelTXT record. When it doesn't, deed.page asks for a_deed-page.<hostname>TXT record with adeed-verify=...value instead. Create every TXT record in the list.
Calling the same POST again for the same slug and hostname is safe. It returns 200 with a fresh status instead of creating a duplicate.
Poll until active
After the records exist, check status. This call also re-runs verification, re-reads the DNS config, and checks the certificate:
curl -sS "https://deed.page/v1/sites/$SLUG/domains/docs.example.com" \
-H "Authorization: Bearer $DEEDPAGE_TOKEN"status moves through these values:
pending_dns: routing records are missing or wrong.status_reasonsays which ones to create.verifying: DNS is in place, but ownership verification hasn't finished.issuing_cert: ownership is confirmed, and the TLS certificate isn't ready yet.active:"Serving this site."The hostname now serves the same site ashttps://{slug}.deed.page/.error: a hard failure, with the reason instatus_reason.
DNS can take anywhere from minutes to hours to propagate. Poll every few minutes, not every second, so the agent stays under the 60-calls-an-hour cap. An agent with a webhook registered gets a domain.active event when the hostname goes live, so it doesn't need to poll at all.
To see every hostname on a site, along with your quota, call GET /v1/sites/$SLUG/domains:
{"ok":true,"domains":[{"domain":"docs.example.com","status":"pending_dns",...}],
"domain_limit":1,"domains_used":1}Remove a hostname
curl -sS -X DELETE "https://deed.page/v1/sites/$SLUG/domains/docs.example.com" \
-H "Authorization: Bearer $DEEDPAGE_TOKEN"That call returned {"ok":true,"removed":"docs.example.com","slug":"my-docs"}. Requests to that host stop serving the site, and the slot frees up. Repeating the call returns 404 not_found. Deleting the whole site also detaches every hostname on it. You can delete the DNS records afterwards.
The same thing over MCP
The MCP server at https://deed.page/mcp has four domain tools: add_domain, domain_status, list_domains, and remove_domain. They return the same JSON as REST, in structuredContent:
curl -sS https://deed.page/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H "Authorization: Bearer $DEEDPAGE_TOKEN" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"add_domain",
"arguments":{"slug":"'"$SLUG"'","domain":"docs.example.com"}}}'In a chat client that already has deed.page connected (see the MCP walkthrough), the agent just calls add_domain, shows the person the records, and calls domain_status later. If the client can't send headers, pass the token as a token argument. Without any token, the tools return isError: true with unauthorized.
Errors an agent should handle
| Status | code | When |
|---|---|---|
| 400 | domain_invalid | Not a bare hostname. https://docs.example.com/x is rejected: send no scheme, path, port, or wildcard. |
| 400 | domain_reserved | deed.page or any subdomain of it. The site already lives at {slug}.deed.page. |
| 401 | unauthorized | No Bearer token. |
| 403 | forbidden | The token doesn't own that slug. |
| 403 | quota_domains | The plan's hostname limit is used up. The message names the upgrade path. |
| 409 | domain_taken | Another site already has that hostname. |
| 429 | rate_limited | Over 60 domain calls an hour. Wait retry_after seconds. |
On Free, a second hostname returns quota_domains with "Free allows 1 custom domain. Hobby ($3/mo) includes 3 custom domains." plus the checkout call. POST /api/v1/billing/checkout with {"plan":"hobby"}, or the MCP tool get_upgrade_link, returns a Stripe Checkout link for a person to pay. Upgrading keeps the same token, sites, and domains. Plans are listed at deed.page/pricing.
A short checklist for agents
1. Publish the site and keep the token.
2. add_domain, then show the person dns_records exactly as returned.
3. Poll domain_status every few minutes, or wait for the domain.active webhook.
4. Report https://<hostname>/ only once status is active. Until then, the {slug}.deed.page URL keeps working.