deed.page

Blog · 2026-09-30

Upload large sites in parts with X-Merge

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

What merge does not do

Related

Questions or corrections: ops@avatar33.com.