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.

Alternative API Viewers

ViewerURL
Redocly hostedredocly.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.

FormatSource
YAMLopenapi.yaml
JSONopenapi.json
Live endpointbrewpage.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)

TagDescriptionKey Endpoints
HTMLHTML/Markdown page hostingPOST /api/html, GET /api/html/{ns}/{id}
KVKey-value store (1000 keys/store)PUT /api/kv/{ns}/{id}/{key}, GET /api/kv/{ns}/{id}/{key}
JSONJSON document hostingPOST /api/json, GET /api/json/{ns}/{id}, PUT /api/json/{ns}/{id}
FilesFile hosting (5 MiB/file)POST /api/files, GET /api/files/{ns}/{id}, DELETE /api/files/{ns}/{id}
SitesMulti-file HTML site hostingPOST /api/sites, GET /api/sites/{ns}/{id}, GET /api/sites/{ns}/{id}/files/{filePath}, DELETE /api/sites/{ns}/{id}
GalleryPublic discovery or owner-scoped listingGET /api/gallery
Hosting ManagementOwner-only delivery, access and publication actionsGET/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 TokenNew token; profile tokens with an admin tokenGET /api/owner-token, POST /api/owner-token/{bind,mint,revoke,revoke-others}
StatsPlatform statisticsGET /api/stats
LimitsCurrent upload limitsGET /api/limits
Short LinksURL shortenerGET /s/{code}
SEOSearch engine supportGET /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:

MethodExample
HeaderX-Owner-Token: tok_xxx
HeaderX-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:

MethodExample
HeaderX-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

ResourceMax SizeMax Count
HTML page5 MiB—
KV value1 MiB1,000 keys/store
JSON doc1 MiB10,000 docs/namespace
File5 MiB1,000 files/ns
Site20 MiB total uncompressed, 5 MiB/file200 files/site
Request25 MiB per request—
TTL1–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>"}'