deed.page

Blog · 2026-10-22

Put an agent's site on your own domain — deed.page custom domains over REST and MCP

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

FreeHobby ($3/mo)Pro ($15/mo)
Custom hostnames per token1320

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:

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:

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

StatuscodeWhen
400domain_invalidNot a bare hostname. https://docs.example.com/x is rejected: send no scheme, path, port, or wildcard.
400domain_reserveddeed.page or any subdomain of it. The site already lives at {slug}.deed.page.
401unauthorizedNo Bearer token.
403forbiddenThe token doesn't own that slug.
403quota_domainsThe plan's hostname limit is used up. The message names the upgrade path.
409domain_takenAnother site already has that hostname.
429rate_limitedOver 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.