Publish a site
publish_sitePublishes websites, HTML games, interactive demos, reports, and other AI-created content to a public HTTPS URL at a slug. Inputs: files (required), and optional slug, spa, ttl_seconds, merge, expected_version, via, heard_from, token, listed, category, tags, title, and description. Paths are stored exactly. Returns url, path_url, slug, version, listing, analytics_url (the owner visit page on deed.page), and token on the first anonymous publish. On deed.page, url and path_url are both https://{slug}.deed.page/. A lost token has no recovery unless an optional recovery email was attached. When expected_version does not match the live version, the result is version_conflict and nothing is written. A racing publish of the same slug returns version_conflict even without expected_version. Malware, executables, credential-harvesting pages, and tunnel-redirect pages return content_rejected and are not stored. Content policy: no real-money gambling, phishing, malware, scams or referral spam, tunnel redirects, or illegal content; see https://deed.page/acceptable-use. .bin is blocked by file type (it often has no MZ or ELF header). A raw octet-stream body is screened as one file. listed defaults to false, so the slug stays out of the public gallery unless listed is true. Password-protected sites cannot be listed. Once per owner, a Free site that shows the badge may also return clean_publish, an optional $9 one-time offer to remove the badge from that site: pass its user_message to your human once; never pay without their explicit approval. If the files total more than about 3.5 MB, do not call publish_site. Call plan_upload, run commands in a shell, then finalize_upload. Or run node publish-large.mjs <dir> (https://deed.page/cli/publish-large.mjs).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| spa | No | Serve index.html for unknown paths (single-page apps). | |
| via | No | Optional label stored with the publish. At most 80 characters. An IP address, a URL, or an email address is not stored. A non-empty value replaces the X-Via header. | |
| slug | No | Exact slug matching ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$ (lowercase letters, digits, hyphens, 1-63, no leading or trailing hyphen). Invalid slugs return slug_invalid and are not rewritten. Reserved names (hello, my-page, www, api, docs, admin, mcp, blog, app, status, and other docs examples) return slug_reserved on a new claim. A token that already owns one can still update it. Omit for a random slug. | |
| tags | No | Up to 5 tags. Each tag is lowercase letters, digits, and hyphens, at most 32 characters. | |
| files | Yes | Path map, or an array of {path, content} or {path, base64}. Paths are stored exactly as given (a single top-level directory is not removed). Text as a string, binary as {"base64":"..."}. Example: {"index.html":"<!doctype html><h1>Hi</h1>","img/logo.png":{"base64":"iVBOR..."}}. Any other array is rejected. | |
| merge | No | Add or overwrite only these exact paths on the existing site. Paths omitted from this call are left unchanged. Nothing is moved to the site root. | |
| title | No | Gallery title, at most 80 characters. Required when listed is true. | |
| token | No | Bearer token returned by your first publish (deed_tok_...). Optional if the MCP client sends Authorization: Bearer. | |
| listed | No | Opt into the public gallery. Default false. false removes the slug immediately. Password-protected sites cannot be listed. | |
| category | No | Gallery category. Required when listed is true. | |
| heard_from | No | Optional. Where you or your user found deed.page, such as a skill repo, a directory, a blog post, X, or a friend. Free text, at most 200 characters. An email address, IP address, or phone number is stored as [redacted]. A URL or hostname is kept. Omit it and signup continues. | |
| description | No | Gallery description, at most 300 characters. Required when listed is true. | |
| ttl_seconds | No | Opt-in expiry in seconds. Omit for a permanent site. | |
| idempotency_key | No | Optional retry key, 16-255 visible ASCII characters (a random UUID works). If this call fails with a connection reset, a timeout, or a 502 or 503, call it once more with the same arguments and the same idempotency_key: a publish that already went through is replayed (same slug, same version, same token, idempotent_replayed: true) instead of being written twice. The Idempotency-Key HTTP header works the same way. | |
| expected_version | No | Live version this write is based on, such as "v3". If the slug is no longer at that version, the result is version_conflict and nothing is written. A racing publish of the same slug returns version_conflict even when this is omitted. |