API Reference
Last updated: 2026-10-29
Explore the BrewPage REST API. This backend-generated public specification includes anonymous, password-protected and owner-only operations with their access requirements. Administrative operations are excluded from public artifacts.
Open the API in a viewer
Redocly loads this site's generated public JSON snapshot; Swagger UI reads the deployed backend contract.
Or download the spec directly:
Alternative API Viewers
| Viewer | URL |
|---|---|
| Redocly hosted | redocly.github.io/redoc?url=… |
| Swagger UI (live on server) | brewpage.app/swagger-ui/index.html |
| OpenAPI JSON (live) | brewpage.app/v3/api-docs |
OpenAPI Specification
Download the public OpenAPI 3.1 snapshot. Backend generation owns schema authority; the YAML representation is derived, and the Scalar JSON copy is identical. Export provenance is committed alongside the snapshot.
| Format | Source |
|---|---|
| YAML | openapi.yaml |
| JSON | openapi.json |
| Live endpoint | brewpage.app/v3/api-docs |
POST /api/html takes a JSON wrapper {content, filename?, showTopBar?, format?}; a body format wins over the ?format= query parameter, and PUT ignores a blank body format. A JSON object without content is published as the content itself, with a body-as-content notice.
HTML API reads return stored rendered HTML, or raw code/text with format-specific MIME. The owner-only /api/html/{ns}/{id}/source endpoint returns original authored source; /settings returns publication settings. Browser share links may provide a viewer. JSON reads return the stored JSON value, including objects, arrays, scalars or null; do not assume a metadata envelope. File reads return bytes and accept dl to request attachment delivery.
API Endpoints Overview
Base URL: https://brewpage.app (alias: https://brewdata.app)
| Tag | Description | Key Endpoints |
|---|---|---|
| HTML | HTML/Markdown page hosting | POST /api/html, GET /api/html/{ns}/{id} |
| KV | Key-value store (1000 keys/store) | PUT /api/kv/{ns}/{id}/{key}, GET /api/kv/{ns}/{id}/{key} |
| JSON | JSON document hosting | POST /api/json, GET /api/json/{ns}/{id}, PUT /api/json/{ns}/{id} |
| Files | File hosting (5 MiB/file) | POST /api/files, GET /api/files/{ns}/{id}, DELETE /api/files/{ns}/{id} |
| Sites | Multi-file HTML site hosting | POST /api/sites, GET /api/sites/{ns}/{id}, GET /api/sites/{ns}/{id}/files/{filePath}, DELETE /api/sites/{ns}/{id} |
| Gallery | Public discovery or owner-scoped listing | GET /api/gallery |
| Hosting Management | Owner-only delivery, access and publication actions | GET/PATCH /api/{type}/{ns}/{id}/hosting, POST /api/{type}/{ns}/{id}/access/revoke, POST /api/{type}/{ns}/{id}/{visibility,password,manage-key,extend,rotate} |
| Owner Token | New token; profile tokens with an admin token | GET /api/owner-token, POST /api/owner-token/{bind,mint,revoke,revoke-others} |
| Stats | Platform statistics | GET /api/stats |
| Limits | Current upload limits | GET /api/limits |
| Short Links | URL shortener | GET /s/{code} |
| SEO | Search engine support | GET /sitemap.xml, GET /robots.txt |
Hosting GET supports html, files, json, kv, and sites; hosting PATCH supports only html, files, json, and kv. Reader-access revocation supports all five types.
Authentication
Unprotected publication reads need no credential, including unlisted content whose link is known. Password-protected reads require access; owner listing and mutations require owner authority. Admin endpoints require separate administrative authentication.
Owner mutations use the original token returned at creation, or the management key of that one publication:
| Method | Example |
|---|---|
| Header | X-Owner-Token: tok_xxx |
| Header | X-Manage-Key: bpm_xxx (key from manageUrl, after #k=) |
manageUrl is private: give it only to the owner, never publish it. A token active in a profile also manages the profile’s publications. Old publications are read-only: content writes answer 409 OLD_PUBLICATION_READ_ONLY with createUrl: POST the content there to publish it anew (HTML also adds republishUrl, the browser composer); delete and Hide still work.
Programmatic protected reads:
| Method | Example |
|---|---|
| Header | X-Password: secret |
NEW protected browser links use the clean returned link and a trusted top-level unlock gateway. Never append passwords or owner tokens to NEW share URLs. OLD publications retain historical access behavior, including legacy query-password parsing where supported; this is not the NEW browser unlock mechanism. API and owner operations remain on the apex, not content hosts.
Publication Delivery
REST creation without ns publishes privately: unlisted and not indexed; anyone with the link can still open it. The MCP wrapper defaults to a generated private namespace. ns=public lists HTML, sites and files in the gallery and sitemap, so Google, Bing and AI search can find and index them; public pages are served on the brewpage.app domain for the strongest reach (a site always gets its own subdomain). Never put secrets in a public publication. POST {visibilityUrl} {"listed":false} hides a public publication, link unchanged. Secrets allowed; likely ones are flagged in notices. For a secret: private namespace + password. Password-protected publications, JSON and KV are never listed.
For fresh NEW publications, sites (any namespace) and HTML pages of format html outside public always use Dedicated subdomain; send no X-Delivery-Mode there. Mismatches are reported, not rejected: an explicit path creates the publication on its subdomain with deliveryModeMatched: false and deliveryModeReason: "policy:subdomain-required", and a content update that turns a non-public path page into format html moves it to its subdomain (the returned link; the old path redirects there). X-Delivery-Mode sent with a content update or site republish is ignored and reported as policy:republish-keeps-mode. Each of these responses carries a human-readable deliveryNotice. Everything else in any namespace (Markdown, code/text, files, JSON, KV, public HTML) defaults to Promotion (path) and may select Dedicated subdomain with X-Delivery-Mode: subdomain. Existing publications keep their mode. Every NEW site bundle has a publication-specific host with relative/root-relative paths and native missing-file 404 responses, without an SPA fallback. Promotion has stricter active-document restrictions.
Keep the returned primary link exactly, including root slashes; do not reconstruct a host from namespace or ID. Responses expose cohort, actual/requested/effective modes, deliveryModeMatched, modeLocked, hostingVersion, access, an apex managementLink, and optional deliveryModeReason/deliveryNotice (omitted when nothing applies). When creation returns an existing publication, it retains its own cohort, link, and mode. OLD links remain compatible and show Existing link, with missing or null effective-mode metadata.
POST /api/html and PUT /api/html/{ns}/{id} responses may also include two optional strings. When a public HTML or Markdown page is created without X-Delivery-Mode and its inline code relies on browser features a BrewPage (path) link does not provide, the server publishes it on its own subdomain and returns deliveryModeReason: "auto:<codes>"; requestedDeliveryMode stays null. advisory:<codes> means the address was not changed (explicit path, a content update, or an existing page returned for an identical repost) but such features were found. deliveryNotice is a human-readable sentence explaining the reason. Codes are comma-separated in a fixed order, drawn from storage, cookies, history, downloads, top-navigation, media-capture, pointer-lock, embeds, fullscreen. Both fields are omitted when nothing applies; send X-Delivery-Mode: path to keep a BrewPage link.
Markdown pages render raw HTML through an allowlist: formatting, images, video/audio, details/summary and tables stay; scripts, styles, event handlers, forms, frames/embeds and meta/base/link are removed; links allow http, https, mailto, tel, relative and #anchor URLs, images also data:. A lone unknown tag such as <author> shows as text. Pages published earlier change only when republished.
An eligible NEW publication can change hosting through owner-only PATCH .../hosting with {"deliveryMode":"subdomain","expectedVersion":1}; send the current hostingVersion when known, expectedVersion is optional. The route also accepts sites. Mismatches are reported, not rejected: a stale or omitted version still applies the choice (a stale one adds deliveryModeReason: "policy:version-stale"); sites and non-public html-format HTML pages stay on their subdomain (policy:mode-locked) and OLD links stay unchanged (policy:classic-link), each answered 200 with deliveryModeMatched: false and a deliveryNotice. Wrong owner returns 403, a malformed version (not a positive integer) 400, and a paused real change 503. Current-mode no-op preserves the version; ordinary content updates and site republishing preserve hosting. The reserved publication host survives both switch directions.
Owners can revoke reader access for a NEW protected publication through POST .../access/revoke with X-Owner-Token and no body. Readers must unlock again; other publications and offline copies are unaffected.
Rate Limits
Limits depend on operation and visibility. Handle the running API’s 429 responses and any Retry-After header rather than assuming a single upload quota.
Size Limits
| Resource | Max Size | Max Count |
|---|---|---|
| HTML page | 5 MiB | — |
| KV value | 1 MiB | 1,000 keys/store |
| JSON doc | 1 MiB | 10,000 docs/namespace |
| File | 5 MiB | 1,000 files/ns |
| Site | 20 MiB total uncompressed, 5 MiB/file | 200 files/site |
| Request | 25 MiB per request | — |
| TTL | 1–30 days, 90 with a profile token (default 15) | — |
Sizes are MiB; video uploads allow up to 20 MiB and audio up to 5 MiB. GET /api/limits returns the current values per type (html, json, kv, files, sites) and profileMaxTtlDays. Content over a resource limit answers 400 with a message naming the limit. KV key counts apply per store; JSON and file counts apply per namespace. The running deployment may apply different limits or owner-specific TTL caps. Eligible content may receive automatic TTL extensions; returned expiresAt describes the current expiry.
Site Uploads
GET /api/limits returns the current request and site limits in bytes; read it before uploading a large or multi-file site:
{"requestMaxBytes": 26214400,
"sites": {"maxFiles": 200, "maxFileBytes": 5242880, "maxTotalBytes": 20971520,
"maxArchiveExpansionBytes": 41943040, "maxPathChars": 380, "maxPathBytes": 900,
"maxNameBytes": 200, "defaultTtlDays": 15, "maxTtlDays": 30,
"allowedExtensions": ["jpg", "png", "html", "css", "js", "..."],
"skippedFileNames": [".DS_Store", "Thumbs.db", "..."],
"skippedRootFolders": ["__MACOSX/", ".Spotlight-V100/", ".Trashes/", ".fseventsd/"]}}
maxTotalBytes counts uncompressed files; maxArchiveExpansionBytes bounds every inflated ZIP byte, skipped system files included. Files named in skippedFileNames or under skippedRootFolders are dropped and never count. Files with an unsupported extension or a too-long path are skipped and listed in the response notices. The entry is the entry parameter, else the root index.html, else the shallowest HTML file; without any HTML file the upload answers 400.
A site limit hit answers 400 with a message naming the limit and what to do. 413 means the request is over 25 MiB, has too many multipart parts (upload a ZIP instead) or a part header (file name/path) that is too long. 422 means no servable file is left (every file has an unsupported or missing extension). A truncated or unreadable ZIP, a ZIP with non-UTF-8 file names, a password-protected ZIP or a path with a .. segment answers 400.
Unstorable metadata is stored as the closest valid value and reported in notices: control characters are removed from titles, tags and file names, over-long tags are cut to fit, and NUL characters in page content are saved as U+FFFD. A value that still cannot be stored answers 400, not 409; 409 remains for conflicting concurrent changes and count limits. If a client aborts an upload before the body is complete, the edge answers 408 with a JSON error body; retry the upload.
Try It Now
curl https://brewpage.app/api/stats
curl -X POST 'https://brewpage.app/api/html?ns=public&format=html' \
-H "Content-Type: application/json" \
-d '{"content":"<h1>Hello!</h1>"}'
Links
- brewpage.app — Live platform
- Swagger UI — Built-in API explorer
- Getting Started — Quick start guide
- Examples — Shell, JavaScript, and Python examples
- MCP Server — AI assistant integration