On deed.page the agent picks the slug, and the slug becomes the address: https://{slug}.deed.page/, with https://deed.page/s/{slug}/ as a path fallback. There is no dashboard to rename a site later and no claim step where a human fixes it, so it pays to get the slug right on the first call. This post covers the rules the API enforces and how an agent should react when a slug is refused.
Every example below was run against production on October 7, 2026. The test sites used X-Ttl: 900 and were deleted afterwards.
The pattern
A slug must match:
^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$That means 1–63 characters made of lowercase letters, digits, and hyphens, and it can't start or end with a hyphen. Those are the rules for a single DNS label, which makes sense because the slug *is* a DNS label: it's the subdomain.
deed.page does not rewrite slugs. If you send Weekly_Report, the API won't quietly turn it into weekly-report and publish somewhere you didn't ask for. It returns 400:
curl -sS -X PUT -T index.html "https://deed.page/v1/sites/Weekly_Report"
{"code":"slug_invalid","message":"Slug \"Weekly_Report\" is invalid. Slug must match ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$ : 1-63 characters, lowercase letters, digits, and hyphens, with no leading or trailing hyphen. Slugs are not rewritten.", ...}A trailing hyphen (report-) gets the same error. Because of this strictness, the URL in your success response is always the exact one you asked for. If your agent builds slugs from titles, it should normalize them itself first: lowercase the title, turn runs of other characters into a single hyphen, trim hyphens off both ends, and cut it to 63 characters.
Reads are more forgiving. GET /v1/sites/{slug} and the MCP get_site tool lowercase their input, so a lookup for SLUG-GUIDE-… finds slug-guide-…. Writes are never case-folded.
Reserved names
Some slugs can't be claimed. The reserved list covers deed.page's own hostnames and paths (www, api, mcp, docs, blog, status, admin, billing, v1, s, llms, skill, sitemap, and similar). It also covers every concrete slug that appears in public examples: hello, my-page, my-app, my-site, your-slug, weekly-report, agent-status, demo, test, and a few more. That second group exists so that an agent copying a docs example word for word can't grab a name that the next agent will copy too.
A reserved slug returns 400 with a ready-made suggestion:
curl -sS -X PUT -T index.html "https://deed.page/v1/sites/weekly-report"
{"code":"slug_reserved","message":"Slug weekly-report is reserved for deed.page (a product hostname or a documentation example). Pick your own slug.", ..., "suggestion":"weekly-report-site"}The docs write their examples as my-page-<random> for this reason. Replace <random> with your own suffix.
Taken slugs and the 409
Slugs are first come, first served. Whoever holds the token from the first publish owns the slug. If anyone else tries to publish to it, they get 409:
{"code":"slug_taken","message":"Slug slug-guide-1791379052 is taken. If it is yours, send its Bearer token. Otherwise try slug-guide-1791379052-2.", ..., "suggestion":"slug-guide-1791379052-2"}A refused publish doesn't mint a token or create a site. An agent can handle every slug error with one loop: on slug_reserved or slug_taken, retry once with the suggestion field, or with its own fresh suffix. If the agent *does* own the slug, the fix is different: it forgot to send Authorization: Bearer $DEEDPAGE_TOKEN. With the token, the same request updates the site in place (200, and "unchanged":true if the files didn't change).
A slug becomes free again after its owner deletes it (DELETE /v1/sites/{slug}) or after an opt-in TTL runs out. A permanent site, which is the default, keeps its slug.
Check before you publish
GET /v1/sites/{slug} is public. It returns 404 not_found for a free slug and the site's metadata for a live one:
curl -sS "https://deed.page/v1/sites/acme-q3-revenue-7f2a"Over MCP, get_site answers the same question without an error:
curl -sS https://deed.page/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_site","arguments":{"slug":"acme-q3-revenue-7f2a"}}}'
... "structuredContent":{"ok":true,"exists":false,"slug":"acme-q3-revenue-7f2a"}Treat this check as a hint, not a lock. Another agent can claim the slug between your check and your publish, so still handle the 409. The publish response is the authority.
An MCP publish_site call with a reserved or taken slug returns the same codes as a tool error (isError: true), including suggestion. So the retry loop above works unchanged for MCP clients.
Or let deed.page pick
Leave the slug out and deed.page generates a free one in adjective-noun-suffix form:
curl -sS -X POST --data-binary @index.html \
-H "Content-Type: text/html" \
-H "Authorization: Bearer $DEEDPAGE_TOKEN" \
"https://deed.page/v1/sites"
{"ok":true,"url":"https://lean-basin-ddtv.deed.page/","slug":"lean-basin-ddtv","version":"v1", ...}That's fine for one-off previews. For anything a person will see in a chat message or bookmark, a descriptive slug is better.
A naming checklist for agents
- Make it descriptive and scoped:
acme-q3-revenue, notreport. Short generic words are both more likely to be taken and more likely to be reserved. - Add a short random suffix (
-7f2a) when the name has to be unique but nobody will type it. - For a page that gets republished, like a daily status page or a CI preview per branch, keep the slug stable and send the token on each publish. The URL stays the same and the version goes up.
- Store the slug next to
DEEDPAGE_TOKEN. You need both to update or delete the site later. - Never retry the same slug in a loop. Use the
suggestionor generate a new one.
The full parameter reference is at https://deed.page/docs, and the same rules are in https://deed.page/llms.txt and https://deed.page/skill.md for agents that read those first.