ccc-pages — self-hosted artifact pages + git-bundle continuity PUT /a/ upload HTML (Bearer, owner — or a PUBLISHER grant cookie + 'x-ccc-auth: cookie') PUT /a/ + 'x-page-archive: tar.gz' — publish a FILE SET from a ustar tar.gz; entries land at /a// POST /a//stage presigned PUT for a big archive (Bearer, owner) POST /a//commit expand the staged archive {key} (Bearer, owner) PATCH /a/ metadata / switch (Bearer, owner — or publisher grant cookie; 'x-page-version: ' switches the live version) GET /a/ view the ACTIVE version (public; sets serve index.html) GET /a// file inside the active set (public) GET /a//v list versions + active (Bearer, owner) GET /a//v/ pinned view — file versions only (public) DELETE /a//v/ delete a version (never the live one) DELETE /a/ delete the page: every version, its /d/ and /f/ store, its bus, its tokens, channel bindings and inbox secret (Bearer, owner; the /r/ bundle stays) GET /d/ list a page's objects (public; ?after= ?limit= ?prefix= ?values=1 — values pages cap at 100) GET /d/ + Upgrade: websocket — LIVE: every change to the page's records, pushed as it lands (gated like every /d/ read; a cookie socket must come from this origin; ?key= ?prefix=

repeatable, ≤ 32; ?tail=). Frames: hello {seq, keys, floors}, then put/del/seq; send only {"type":"ping"}. 60 upgrades per 10 s per ip; 256 sockets per page GET /d// read one object (public) GET /d//~gallery VIRTUAL record: the page owner's live gallery (pages + bundles, the /gallery shape) — only on a page flagged 'x-page-gallery: on' (PATCH, owner); gated like every /d/ read, never writable GET /d//~ COMPUTED record: the page's active handler's view over the page's own records (?k=v params, ≤ 16); declared in handler.active's views; gated like every /d/ read, ETag + If-None-Match → 304, never writable; 100 reads per 10 s per ip PUT /d// write one object (Bearer, owner; ?v=|force — or an EDITOR access token, with 'x-ccc-auth: cookie'; a CONTRIBUTOR token creates a key and then writes only the keys its token created) DELETE /d// delete one object (Bearer, owner; editor token too) POST /f/ upload a file (same writers as /d/; body = the bytes, content-type = the stored type; ?name= ?description= ?meta= ?notify=|bound; raster images get a thumbnail; no expiry) POST /f/?multipart=1&bytes=&part_bytes=&sha256= start a multipart upload (files over 100 MiB; no body; ?name= ?description= ?meta= as above) PUT /f///parts/ one part (part_bytes each, the last the remainder; the record's 'parts' lists what landed) POST /f///complete assemble the parts, state → ready (?notify=) GET /f// 302 to a presigned R2 link, valid 3 h (gated like the page; inline for safe types, attachment otherwise; Range ok at the link; HEAD answers here; 409 not_ready while a multipart upload is open) GET /f///thumb 512px JPEG thumbnail (images only) DELETE /f// delete file + record (same writers as /d/; aborts an open multipart upload) POST /i/ deliver an event (x-inbox-token; x-inbox-event names it, x-inbox-delivery dedupes it; private pages too) PUT|GET|DELETE /i//secret mint, read or drop the inbox secret (Bearer, owner) POST /i//handler/replay fold the candidate over retained rows, one page of at most 100 per call (Bearer, owner) GET|POST|PUT|PATCH|DELETE /h/[/] the page's activated request handler — its request(req, ctx) over the path, query and body; ctx names the caller (owner Bearer, or every live grant cookie covering the page; 'x-ccc-auth: cookie' on a cookie write); JSON in, JSON out; fails closed; 100 requests per 10 s per ip PUT /r/ push git bundle (Bearer) GET /r/[/v/] fetch git bundle (Bearer, owner) GET /r//info bundle metadata (Bearer, owner) GET /lib/@/ pinned npm asset (public read; Bearer to mirror) GET /client/@.js client library (public read, revalidated) GET /gallery your pages + bundles (Bearer) POST /t mint a page access token (Bearer, owner — or a PUBLISHER grant cookie + 'x-ccc-auth: cookie', for pages inside its scope; a '*' scope is Bearer-only; {scope: |'*', role?, label?, expires?} — the secret is returned exactly once) GET /t list your access tokens (Bearer, or a publisher grant for the tokens it covers; never the hash) PATCH /t/ edit scope/role/label/expires, or {revoked: true|false} DELETE /t/ delete a token outright (revoke is restorable; this is not) POST /unlock spend a share token (public; {slug, token} — sets a per-grant HttpOnly cookie for 60 minutes) POST /lock drop every grant cookie (public; the logout) GET /b/ the page's device bus: seq, oldest, peers with presence (gated like the page) WS /b//peer?id= a device socket ('x-page-token: ' of a DEVICE token — page-token mint --role device) WS /b//view a browser socket (owner Bearer, or a grant covering the page from this Origin; public pages admit read-only viewers) WS /m/report?host=&os=&daemon= a usage reporter (Bearer; one hub per person — the Bearer's login) PUT /m/page register the page that reads your usage hub (Bearer; {slug} of your private page) DELETE /m/account?provider=&account= drop one account from your hub (Bearer) DELETE /m/host?host= drop a host that is not reporting now from your hub (Bearer) GET /m/state?provider=&profile= your usage hub, read with your Bearer or a USAGE token — page-token mint '*' --role usage (server-side; both filters optional) GET /m//state the usage hub, read through the registered private page (gated like the page) WS /m//view the same, live (owner Bearer, or a grant covering the page from this Origin) GET /s your user space: every document's metadata (Bearer; ?scope=user|host/ ?prefix= narrow it) GET /s/user/ one document of your user scope, as stored (Bearer; HEAD for the headers alone) GET /s/host// one document of that host's scope (Bearer) PUT /s/…/ write one document (Bearer; ?v=|force, 0 = must not exist; body = the bytes, ≤ 256 KiB; content-type kept, none = markdown; 'x-space-host' names the writer; 'x-space-tokens' + 'x-space-session' + 'x-space-model', all three or none, are its measurement — replaced by every put; ucc-space sends them from an agent's call) DELETE /s/…/ delete one document (Bearer; ?v=|force) POST /c open an interactive channel (Bearer) GET /c/ channel status (channel id is the capability) WS /c//agent agent socket (Bearer, owner) WS /c//page page socket (channel id only) POST /u/ presigned upload URL (channel id only; {channel, bytes, filename?}) GET /u// download an upload (the key is the capability) DELETE /u// delete an upload (Bearer, owner) GET /healthz liveness (public; {ok, service}) Bearer = : — your ucc identity, no separate token. A page published with 'x-page-visibility: private' is readable only by its owner and by holders of an access token (POST /t → a #t= share link → POST /unlock). Private is a PUBLISH-TIME choice: PATCH cannot turn it on, because what is public is already public. Delete and republish instead. /d/ is opt-in per page (PATCH /a/ with 'x-page-data: on') and needs a slug carrying a random segment of 16+ characters. A write may ping up to 4 watching channels with '¬ify=' so open browsers re-read. Writing the 'release' key needs no '¬ify=': it pings the channels this page's owner already bound to the slug (POST /c with {slug}), which is how a plain publish reaches a tab that was already open. Any other key gets the same on request with '¬ify=bound' (owner Bearer only; /d/ and /f/ writes alike). A file uploaded through /f/ IS a /d/ record: key 'f.', schema file@1, bytes in R2 with no expiry (unlike /u/ browser attachments, which expire after 30 days). List a page's files in one call: GET /d/?prefix=f.&values=1. Edit metadata with a normal /d/ PUT on the record; deleting the record (either door) deletes the bytes. Details: FILES.md in the repo.