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// 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') 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) GET /f// file bytes (gated like the page; inline for safe types, attachment otherwise; Range ok) GET /f///thumb 512px JPEG thumbnail (images only) DELETE /f// delete file + record (same writers as /d/) 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) 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.