Sites
One-shot publishing of static HTML bundles to plip.cc
Sites
The Sites module is a parallel app under /sites for publishing static page
bundles. It’s designed for agents and small tools that generate HTML and
need a quick share link — upload a zip, get back a public URL on
plip.cc.
Each upload becomes an immutable Site with a short, unguessable slug.
Bundles live on a separate registrable domain (plip.cc) so uploaded
HTML/JS can never read cookies, hit APIs, or otherwise touch your
bunmori.com admin app — full origin isolation.
What you get
- One-shot publishing — POST a zip, get back
https://plip.cc/{slug}/. - User-scoped publishing tokens — one token per agent, used to create many sites under your account.
- Web upload — the admin UI at
/sites/newdoes the same flow from a browser. - Anonymous public read — anyone with the URL can view the site. No
account, no login. Pages are served with
noindexso they don’t show up in search. - File-type allowlist — only standard page assets (HTML, CSS, JS, images, fonts, JSON, SVG, fonts, PDF, …) are accepted. Executables, archives, and unknown binaries are rejected before upload.
- Strong limits — per-file 10 MB, per-site 50 MB and 200 files, per-user 100 sites and 2 GB.
Security model
- Cross-site by design.
plip.ccandbunmori.comare different registrable domains, so uploaded JS can’t read your admin cookies or make credentialed requests against the admin API. - No content scanning. Pre-scanning HTML/JS for malicious behaviour is unreliable and trivially evaded. Defence is architectural: origin isolation, the file-extension allowlist, per-user quotas, and the ability to revoke a token / delete a site in seconds.
noindexby default. Sites are meant for sharing by link, not for discovery via search engines. The serve route always setsX-Robots-Tag: noindex, nofollow.- No
Domaincookies. Auth.js cookies are host-only, so they never reach plip.cc.
API reference
All requests use bearer-token auth. Tokens are created at
/sites/tokens in the admin UI and start with the prefix bs_.
POST /api/public/sites
Upload a zip bundle. Returns the created site’s metadata and public URL.
curl -X POST https://bunmori.com/api/public/sites \
-H "Authorization: Bearer bs_xxxxx" \
-F "bundle=@./site.zip" \
-F "title=My share" \
-F "indexPath=index.html"Response (201):
{
"id": "...",
"slug": "abc12345",
"url": "https://plip.cc/abc12345/",
"fileCount": 4,
"totalBytes": 12345
}Errors:
400— invalid zip, disallowed extension, path traversal, missing entry file, zip-bomb compression ratio exceeded.401— missing or invalid token.413— bundle exceeds per-site or per-user storage quota.429— upload rate limit exceeded.
GET /api/public/sites
List all sites owned by the token’s user.
GET /api/public/sites/{slug}
Metadata for one site.
DELETE /api/public/sites/{slug}
Delete a site (and all its R2 files). The public URL returns 404 immediately.
Limits
| Limit | Value |
|---|---|
| Per file | 10 MB |
| Per site | 50 MB and 200 files |
| Per user | 100 sites and 2 GB total |
| Upload rate | 20 uploads / hour / user |
| Tokens | 10 publishing tokens / user |
| Zip compression ratio | 100× (rejected as zip bomb above this) |
Allowed file types
html htm css js mjs map json svg png jpg jpeg gif webp avif ico woff woff2 ttf otf eot txt md pdf xml webmanifest
Anything else is rejected at upload. Need another extension? Open an issue.