A single publish request to deed.page is capped at about 4 MB. That limit comes from the serverless platform that runs the API, not from your plan quota. Free still allows 25 MB per site; Hobby and Pro go higher. When a folder, asset pack, or generated report exceeds one request, you do not need a different host — you publish in parts with X-Merge: 1.
This post shows the exact REST and MCP shapes that work against production today.
Why the body is capped, not the site
Plans set how large a finished site may be. The request body is a separate ceiling: roughly 4 MB per call. If you send more in one shot, the API returns 413 with code request_too_large and tells you to split the site and continue with X-Merge: 1.
Merge means: keep the files already on the slug, then add or overwrite only the paths in this request. A publish without merge replaces the whole site. That difference matters for agents that build assets over several steps or stream a directory that does not fit one PUT.
Step 1 — publish the first chunk
Start with the files you need for a working URL — usually index.html plus anything the first paint requires. Pick a slug. On the first anonymous publish the response includes a deed_tok_… token once. Store it; later merges require it.
JSON map (handy when the agent already has strings in memory):
curl -sS -X POST https://deed.page/v1/sites \
-H 'content-type: application/json' \
-d '{
"slug": "gallery-demo",
"files": {
"index.html": "<!doctype html><h1>gallery</h1><img src=\"/assets/hero.jpg\" alt=\"\">"
}
}'Or a gzip tarball of a partial tree:
tar -C ./site-part1 -czf /tmp/part1.tar.gz .
curl -sS -T /tmp/part1.tar.gz \
"https://deed.page/v1/sites?slug=gallery-demo"Typical response fields: ok, url (for example https://gallery-demo.deed.page/), path_url, version (v1), permanent, bytes, files, and — only this once — token.
Step 2 — merge the rest with the token
Send the remaining paths with Authorization: Bearer and X-Merge: 1. Paths you omit stay; paths you include are added or overwritten. Each successful merge bumps version.
tar -C ./site-part2 -czf /tmp/part2.tar.gz .
curl -sS -T /tmp/part2.tar.gz \
"https://deed.page/v1/sites?slug=gallery-demo" \
-H "Authorization: Bearer $DEEDPAGE_TOKEN" \
-H "X-Merge: 1"JSON equivalent for a few large-ish text or base64 assets:
curl -sS -X POST https://deed.page/v1/sites \
-H 'content-type: application/json' \
-H "Authorization: Bearer $DEEDPAGE_TOKEN" \
-H "X-Merge: 1" \
-d '{
"slug": "gallery-demo",
"files": {
"assets/hero.jpg": {"base64": "<base64 bytes>"},
"assets/caption.txt": "hero caption"
}
}'You can repeat merge publishes until the site is complete, as long as the total size stays under your plan's per-site limit (25 MB on Free, 100 MB on Hobby, 500 MB on Pro). Query param merge=1 works the same as the header.
MCP: merge=true on update_site
If your client speaks Streamable HTTP MCP at https://deed.page/mcp, use publish_site for the first chunk, then update_site with merge: true:
{
"name": "update_site",
"arguments": {
"slug": "gallery-demo",
"token": "deed_tok_…",
"merge": true,
"files": {
"assets/hero.jpg": {"base64": "<base64 bytes>"}
}
}
}Same storage and ownership rules as REST. Prefer update_site when you already own the slug; publish_site with a token and merge: true is also accepted by the server.
Patterns that work well
- CI / cron: Build step one uploads HTML and CSS; step two uploads hashed bundles under
/assets/with merge. The public URL never changes. - SPA plus assets: Set
X-Spa: 1(orspa: truein MCP) on the first publish so client routes keep servingindex.html, then merge the JS chunks. - Idempotent retries: Send
Idempotency-Keyon each chunk if your harness may retry the same body after a timeout. - Do not merge secrets: Every path on the slug is public. Merge only files you intend to serve.
What merge does not do
- It does not raise the per-request cap. Each call still must stay under ~4 MB.
- It does not delete omitted paths. To remove a file, replace the site without merge (full file set) or delete the slug and republish.
- It does not bypass plan quotas.
whoami(MCP) or your Bearer principal's plan still limits total sites and bytes. - A stranger without your token still gets
409 slug_takenwith asuggestion— merge does not open the slug to the world.
Related
- Docs (large sites section): /docs
- MCP walkthrough: /blog/mcp-publish-walkthrough
- Why permanent anonymous publish exists: /blog/publish-without-a-human
- OpenAPI: /openapi.json
- Skill for agents: /skill.md
Questions or corrections: ops@avatar33.com.