openapi: 3.1.0
info:
title: BrewPage API
description: "Free instant hosting for HTML, Markdown, AI artifacts and files"
version: 1.33.3
servers:
- url: https://brewpage.app
description: Generated server url
tags:
- name: preview
description: Per-content OpenGraph image (1200×630 PNG)
- name: Short Links
description: Short URL resolver for sharing
- name: Owner Check
description: Lightweight owner-token probe; never increments views or returns content
- name: HTML
description: HTML page hosting with markdown support
- name: KV
description: Key-Value store with up to 1000 keys per namespace
- name: Sites
description: Multi-file HTML site hosting via ZIP or folder upload
- name: SEO
description: Search engine optimization endpoints
- name: Gallery
description: Browse public content from the 'public' namespace without password
protection
- name: JSON
description: "JSON document store with up to 10,000 docs per collection"
- name: Stats
description: Platform-wide usage statistics
- name: Namespace
description: "Fresh, collision-free namespace suggestions"
- name: preview
description: OpenGraph metadata for social bots
- name: Files
description: "File hosting up to 5 MB per file, 1000 files per namespace"
- name: Reports
description: Abuse reports for hosted content
paths:
/api/kv/{ns}/{id}/{key}:
get:
tags:
- KV
summary: Get key value
description: Returns the value and last update timestamp for a specific key
operationId: getKey
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: key
in: path
required: true
schema:
type: string
- name: X-Password
in: header
description: Access password via header
required: false
schema:
type: string
- name: p
in: query
description: Access password via query param (alternative to X-Password header)
required: false
schema:
type: string
responses:
"200":
description: Key value
content:
'*/*':
schema:
$ref: "#/components/schemas/KvGetResponse"
"403":
description: Wrong password
content:
'*/*':
schema:
$ref: "#/components/schemas/KvGetResponse"
"404":
description: Store or key not found
content:
'*/*':
schema:
$ref: "#/components/schemas/KvGetResponse"
put:
tags:
- KV
summary: Upsert key
description: Creates or updates a key in the store; max 1000 keys per store
operationId: upsertKey
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: key
in: path
required: true
schema:
type: string
- name: X-Owner-Token
in: header
description: Owner token returned at creation. Required for update and delete
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/KvUpsertKeyRequest"
required: true
responses:
"200":
description: Key upserted
content:
'*/*':
schema:
$ref: "#/components/schemas/KvUpsertKeyResponse"
"403":
description: Missing or wrong owner token
content:
'*/*':
schema:
$ref: "#/components/schemas/KvUpsertKeyResponse"
"404":
description: Store not found or expired
content:
'*/*':
schema:
$ref: "#/components/schemas/KvUpsertKeyResponse"
"409":
description: Key limit exceeded
content:
'*/*':
schema:
$ref: "#/components/schemas/KvUpsertKeyResponse"
delete:
tags:
- KV
summary: Delete key
description: Removes a single key from the store; deleting the last key does
not remove the store
operationId: deleteKey
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: key
in: path
required: true
schema:
type: string
- name: X-Owner-Token
in: header
description: Owner token returned at creation. Required for update and delete
required: true
schema:
type: string
responses:
"204":
description: Key deleted
"403":
description: Missing or wrong owner token
"404":
description: Store or key not found
/api/json/{ns}/{id}:
get:
tags:
- JSON
summary: Get JSON document
description: Returns raw JSON content with application/json content type
operationId: getById
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: X-Password
in: header
description: Access password via header
required: false
schema:
type: string
- name: p
in: query
description: Access password via query param (alternative to X-Password header)
required: false
schema:
type: string
responses:
"200":
description: JSON content
content:
'*/*':
schema:
type: string
"403":
description: Wrong password
content:
'*/*':
schema:
type: string
"404":
description: Document not found or expired
content:
'*/*':
schema:
type: string
put:
tags:
- JSON
summary: Update JSON document
description: Replaces document content; requires the owner token returned at
creation
operationId: update
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: X-Owner-Token
in: header
description: Owner token returned at creation. Required for update and delete
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
type: string
required: true
responses:
"200":
description: Document updated
content:
'*/*':
schema:
$ref: "#/components/schemas/JsonUpdateResponse"
"403":
description: Missing or wrong owner token
content:
'*/*':
schema:
$ref: "#/components/schemas/JsonUpdateResponse"
"404":
description: Document not found or expired
content:
'*/*':
schema:
$ref: "#/components/schemas/JsonUpdateResponse"
delete:
tags:
- JSON
summary: Delete JSON document
description: Permanently removes the document
operationId: delete
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: X-Owner-Token
in: header
description: Owner token returned at creation. Required for update and delete
required: true
schema:
type: string
responses:
"204":
description: Document deleted
"403":
description: Missing or wrong owner token
"404":
description: Document not found or expired
/api/html/{ns}/{id}:
get:
tags:
- HTML
summary: Get HTML page
description: Returns rendered HTML content; password-protected pages require
X-Password header or ?p= query param
operationId: getById_1
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: X-Password
in: header
description: Access password via header
required: false
schema:
type: string
- name: p
in: query
description: Access password via query param (alternative to X-Password header)
required: false
schema:
type: string
responses:
"200":
description: HTML content
content:
'*/*':
schema:
type: string
"403":
description: Wrong password
content:
'*/*':
schema:
type: string
"404":
description: Page not found or expired
content:
'*/*':
schema:
type: string
put:
tags:
- HTML
summary: Update HTML page
description: Replaces page content; requires the owner token returned at creation
operationId: update_1
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: X-Owner-Token
in: header
description: Owner token returned at creation. Required for update and delete
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/HtmlUpdateRequest"
required: true
responses:
"200":
description: Page updated
content:
'*/*':
schema:
$ref: "#/components/schemas/HtmlUpdateResponse"
"403":
description: Missing or wrong owner token
content:
'*/*':
schema:
$ref: "#/components/schemas/HtmlUpdateResponse"
"404":
description: Page not found or expired
content:
'*/*':
schema:
$ref: "#/components/schemas/HtmlUpdateResponse"
delete:
tags:
- HTML
summary: Delete HTML page
description: Permanently removes page and frees the short URL
operationId: delete_1
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: X-Owner-Token
in: header
description: Owner token returned at creation. Required for update and delete
required: true
schema:
type: string
responses:
"204":
description: Page deleted
"403":
description: Missing or wrong owner token
"404":
description: Page not found or expired
/api/sites:
post:
tags:
- Sites
summary: Upload site
description: Upload a multi-file HTML site as a ZIP archive or as individual
files with paths. Returns a link to the published site
operationId: upload
parameters:
- name: files
in: query
description: Individual files (alternative to archive)
required: false
schema:
type: array
items:
type: string
format: binary
- name: paths
in: query
description: Relative paths for each file (must match files order)
required: false
schema:
type: array
items:
type: string
- name: ns
in: query
description: "Namespace. Default: public"
required: false
schema:
type: string
default: public
pattern: "^[a-z0-9-]{1,32}$"
- name: tags
in: query
description: Comma-separated tags
required: false
schema:
type: string
example: "demo,portfolio"
- name: ttl
in: query
description: "Time to live in days (1-30, default 5)"
required: false
schema:
type: integer
format: int32
- name: entry
in: query
description: "Entry file path override (default: auto-detect index.html)"
required: false
schema:
type: string
- name: X-Password
in: header
description: Access password
required: false
schema:
type: string
- name: X-Owner-Token
in: header
description: Reuse existing owner token to group entities under one owner
required: false
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
properties:
archive:
type: string
format: binary
description: ZIP archive containing site files
responses:
"201":
description: Site uploaded
content:
'*/*':
schema:
$ref: "#/components/schemas/SiteUploadResponse"
"400":
description: "Invalid request, no HTML files, or limits exceeded"
content:
'*/*':
schema:
$ref: "#/components/schemas/SiteUploadResponse"
"415":
description: Unsupported file type in archive
content:
'*/*':
schema:
$ref: "#/components/schemas/SiteUploadResponse"
"429":
description: Rate limit exceeded
content:
'*/*':
schema:
$ref: "#/components/schemas/SiteUploadResponse"
/api/reports:
post:
tags:
- Reports
summary: Submit abuse report
description: Records a public report about a resource hosted on brewpage.app
for moderator review
operationId: create
parameters:
- name: User-Agent
in: header
required: false
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/ReportRequest"
required: true
responses:
"201":
description: Report accepted
content:
'*/*':
schema:
$ref: "#/components/schemas/ReportResponse"
"400":
description: Invalid request parameters
content:
'*/*':
schema:
$ref: "#/components/schemas/ReportResponse"
/api/kv:
get:
tags:
- KV
summary: List KV stores
description: Returns all KV stores owned by the given token in the namespace.
Returns empty list without token
operationId: listStores
parameters:
- name: ns
in: query
description: Namespace
required: false
schema:
type: string
default: public
pattern: "^[a-z0-9-]{1,32}$"
- name: X-Owner-Token
in: header
description: Owner token to filter by ownership. Without token returns empty
list
required: false
schema:
type: string
responses:
"200":
description: Store list
content:
'*/*':
schema:
type: array
items:
$ref: "#/components/schemas/KvStoreListResponse"
post:
tags:
- KV
summary: Create KV store
description: Creates a new store with an initial key-value pair and returns
a shareable link. Reuse existing owner token to group entities under one owner
operationId: createStore
parameters:
- name: ns
in: query
description: "Namespace. Default: public. Pages in 'public' without password\
\ appear in gallery. Custom namespace is created automatically"
required: false
schema:
type: string
default: public
pattern: "^[a-z0-9-]{1,32}$"
- name: tags
in: query
description: Comma-separated tags
required: false
schema:
type: string
example: "demo,test"
- name: ttl
in: query
description: "Time to live in days (1-365, default 5). Store auto-deletes\
\ after expiry"
required: false
schema:
type: integer
format: int32
- name: X-Password
in: header
description: "Access password. Empty = public store visible in gallery. With\
\ password = hidden from gallery, viewers must enter password or pass ?p=\
\ in URL"
required: false
schema:
type: string
- name: X-Owner-Token
in: header
description: "Reuse existing owner token to group entities under one owner.\
\ If omitted, a new token is generated"
required: false
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/KvCreateStoreRequest"
required: true
responses:
"201":
description: Store created
content:
'*/*':
schema:
$ref: "#/components/schemas/KvCreateStoreResponse"
"400":
description: Invalid request parameters
content:
'*/*':
schema:
$ref: "#/components/schemas/KvCreateStoreResponse"
"429":
description: Rate limit exceeded
content:
'*/*':
schema:
$ref: "#/components/schemas/KvCreateStoreResponse"
/api/json:
get:
tags:
- JSON
summary: List JSON documents
description: Returns documents owned by the given token. Empty list without
token
operationId: list
parameters:
- name: ns
in: query
description: Namespace
required: false
schema:
type: string
default: public
pattern: "^[a-z0-9-]{1,32}$"
- name: X-Owner-Token
in: header
description: Owner token to filter by ownership. Without token returns empty
list
required: false
schema:
type: string
responses:
"200":
description: Document list
content:
'*/*':
schema:
type: array
items:
$ref: "#/components/schemas/JsonListResponse"
post:
tags:
- JSON
summary: Create JSON document
description: Stores any valid JSON and returns a shareable link. Reuse existing
owner token to group entities under one owner
operationId: create_1
parameters:
- name: ns
in: query
description: "Namespace. Default: public. Pages in 'public' without password\
\ appear in gallery. Custom namespace is created automatically"
required: false
schema:
type: string
default: public
pattern: "^[a-z0-9-]{1,32}$"
- name: tags
in: query
description: Comma-separated tags
required: false
schema:
type: string
example: "demo,test"
- name: ttl
in: query
description: "Time to live in days (1-365, default 5). Document auto-deletes\
\ after expiry"
required: false
schema:
type: integer
format: int32
- name: X-Password
in: header
description: "Access password. Empty = public document visible in gallery.\
\ With password = hidden from gallery, viewers must enter password or pass\
\ ?p= in URL"
required: false
schema:
type: string
- name: X-Owner-Token
in: header
description: "Reuse existing owner token to group entities under one owner.\
\ If omitted, a new token is generated"
required: false
schema:
type: string
requestBody:
content:
application/json:
schema:
type: string
required: true
responses:
"201":
description: Document created
content:
'*/*':
schema:
$ref: "#/components/schemas/JsonCreateResponse"
"400":
description: Invalid JSON or request parameters
content:
'*/*':
schema:
$ref: "#/components/schemas/JsonCreateResponse"
"429":
description: Rate limit exceeded
content:
'*/*':
schema:
$ref: "#/components/schemas/JsonCreateResponse"
/api/html:
post:
tags:
- HTML
summary: Create HTML page
description: Stores HTML or markdown content and returns a shareable link. Reuse
existing owner token to group entities under one owner
operationId: create_2
parameters:
- name: ns
in: query
description: "Namespace. Default: public. Pages in 'public' without password\
\ appear in gallery. Custom namespace is created automatically"
required: false
schema:
type: string
default: public
pattern: "^[a-z0-9-]{1,32}$"
- name: tags
in: query
description: Comma-separated tags
required: false
schema:
type: string
example: "demo,test"
- name: ttl
in: query
description: "Time to live in days (1-365, default 5). Page auto-deletes after\
\ expiry"
required: false
schema:
type: integer
format: int32
- name: format
in: query
description: "Content format: 'html' (default), 'markdown', or 'md'. Markdown\
\ is rendered to styled HTML with github-markdown-css"
required: false
schema:
type: string
default: html
- name: X-Password
in: header
description: "Access password. Empty = public page visible in gallery. With\
\ password = hidden from gallery, viewers must enter password or pass ?p=\
\ in URL"
required: false
schema:
type: string
- name: X-Owner-Token
in: header
description: "Reuse existing owner token to group entities under one owner.\
\ If omitted, a new token is generated"
required: false
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/HtmlUploadRequest"
required: true
responses:
"201":
description: Page created
content:
'*/*':
schema:
$ref: "#/components/schemas/HtmlUploadResponse"
"400":
description: Invalid request parameters
content:
'*/*':
schema:
$ref: "#/components/schemas/HtmlUploadResponse"
"429":
description: Rate limit exceeded
content:
'*/*':
schema:
$ref: "#/components/schemas/HtmlUploadResponse"
/api/files:
get:
tags:
- Files
summary: List files
description: Returns files owned by the given token. Empty list without token
operationId: list_1
parameters:
- name: ns
in: query
description: Namespace
required: false
schema:
type: string
default: public
pattern: "^[a-z0-9-]{1,32}$"
- name: X-Owner-Token
in: header
description: Owner token to filter by ownership. Without token returns empty
list
required: false
schema:
type: string
responses:
"200":
description: File list
content:
'*/*':
schema:
type: array
items:
$ref: "#/components/schemas/FileListResponse"
post:
tags:
- Files
summary: Upload file
description: Stores a file via multipart upload and returns a download link.
Only safe file types accepted. Reuse existing owner token to group entities
under one owner
operationId: upload_1
parameters:
- name: ns
in: query
description: "Namespace. Default: public. Pages in 'public' without password\
\ appear in gallery. Custom namespace is created automatically"
required: false
schema:
type: string
default: public
pattern: "^[a-z0-9-]{1,32}$"
- name: tags
in: query
description: Comma-separated tags
required: false
schema:
type: string
example: "demo,test"
- name: ttl
in: query
description: "Time to live in days (1-365, default 5). File auto-deletes after\
\ expiry"
required: false
schema:
type: integer
format: int32
- name: X-Password
in: header
description: "Access password. Empty = public file visible in gallery. With\
\ password = hidden from gallery, viewers must enter password or pass ?p=\
\ in URL"
required: false
schema:
type: string
- name: X-Owner-Token
in: header
description: "Reuse existing owner token to group entities under one owner.\
\ If omitted, a new token is generated"
required: false
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
properties:
file:
type: string
format: binary
required:
- file
responses:
"201":
description: File uploaded
content:
'*/*':
schema:
$ref: "#/components/schemas/FileUploadResponse"
"400":
description: Invalid request or file too large
content:
'*/*':
schema:
$ref: "#/components/schemas/FileUploadResponse"
"415":
description: Unsupported file type
content:
'*/*':
schema:
$ref: "#/components/schemas/FileUploadResponse"
"429":
description: Rate limit exceeded
content:
'*/*':
schema:
$ref: "#/components/schemas/FileUploadResponse"
/{ns}/{id}:
get:
tags:
- Short Links
operationId: resolve
parameters:
- name: ns
in: path
required: true
schema:
type: string
- name: id
in: path
required: true
schema:
type: string
- name: X-Password
in: header
description: Access password for protected resources
required: false
schema:
type: string
- name: p
in: query
description: Access password (query alternative to X-Password header)
required: false
schema:
type: string
- name: dl
in: query
description: Force download as attachment
required: false
schema:
type: boolean
- name: Range
in: header
required: false
schema:
type: string
- name: X-Resolve
in: header
required: false
schema:
type: string
- name: X-Owner-Token
in: header
description: "Owner token; when supplied, response carries X-Is-Owner: true\
\ on match"
required: false
schema:
type: string
responses:
"200":
description: OK
content:
'*/*':
schema:
type: object
/{ns}/{id}/{sub}:
get:
tags:
- Short Links
operationId: resolveWithSub
parameters:
- name: ns
in: path
required: true
schema:
type: string
- name: id
in: path
required: true
schema:
type: string
- name: sub
in: path
required: true
schema:
type: string
- name: X-Password
in: header
description: Access password for protected resources
required: false
schema:
type: string
- name: p
in: query
description: Access password (query alternative to X-Password header)
required: false
schema:
type: string
- name: dl
in: query
description: Force download as attachment
required: false
schema:
type: boolean
- name: X-Resolve
in: header
required: false
schema:
type: string
- name: X-Owner-Token
in: header
description: "Owner token; when supplied, response carries X-Is-Owner: true\
\ on match"
required: false
schema:
type: string
responses:
"200":
description: OK
content:
'*/*':
schema:
type: object
/{key}.txt:
get:
tags:
- SEO
summary: IndexNow key verification
description: Serves the IndexNow verification key file for search engine crawlers
operationId: serveKeyFile
parameters:
- name: key
in: path
required: true
schema:
type: string
responses:
"200":
description: Key file content
content:
text/plain:
schema:
type: string
"404":
description: Unknown key
content:
text/plain:
schema:
type: string
/preview/{ns}/{id}.png:
get:
tags:
- preview
summary: Per-content OG image
description: "Returns 1200×630 PNG, cached, with ETag/If-None-Match support;\
\ falls back to /og-image.png?v=2 on any failure"
operationId: preview
parameters:
- name: ns
in: path
required: true
schema:
type: string
- name: id
in: path
required: true
schema:
type: string
- name: If-None-Match
in: header
required: false
schema:
type: string
responses:
"200":
description: PNG image bytes
content:
image/png:
schema:
type: object
"302":
description: "Fallback to static og-image.png (flag off, no source, generation\
\ error, oversized)"
content:
image/png:
schema:
type: object
"304":
description: Not modified (If-None-Match matched current ETag)
content:
image/png:
schema:
type: object
"429":
description: Per-IP rate limit exceeded
content:
image/png:
schema:
type: object
/preview-html/{ns}/{id}:
get:
tags:
- preview
summary: OpenGraph HTML stub
description: Tiny HTML response with og:title/og:description/og:image meta tags
for social-bot unfurls
operationId: previewHtml
parameters:
- name: ns
in: path
required: true
schema:
type: string
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: HTML stub with OG meta (or generic stub when resource is missing)
content:
text/html:
schema:
type: string
/api/{ns}/{id}/owner-check:
get:
tags:
- Owner Check
summary: Verify whether the supplied X-Owner-Token owns the resource
description: "Returns `{isOwner, type}`. Constant-time BCrypt match. 404 when\
\ the resource does not exist (or has expired). 200 with `isOwner:false` when\
\ the X-Owner-Token header is absent."
operationId: ownerCheck
parameters:
- name: ns
in: path
required: true
schema:
type: string
- name: id
in: path
required: true
schema:
type: string
- name: X-Owner-Token
in: header
description: Owner token to verify (constant-time match)
required: false
schema:
type: string
responses:
"200":
description: OK
content:
'*/*':
schema:
$ref: "#/components/schemas/OwnerCheckResponse"
/api/stats:
get:
tags:
- Stats
summary: Get platform stats
description: Returns today's and all-time creation/view counts with per-type
breakdown. Optional 'tz' query param (IANA id) controls the boundary of 'today';
defaults to UTC.
operationId: getStats
parameters:
- name: tz
in: query
description: "IANA timezone id for the 'today' boundary. Defaults to UTC.\
\ Example: Europe/Lisbon"
required: false
schema:
type: string
responses:
"200":
description: Platform statistics
content:
'*/*':
schema:
$ref: "#/components/schemas/StatsResponse"
/api/sites/{ns}/{id}:
get:
tags:
- Sites
summary: Get site info
description: Returns site metadata and file list. Requires owner token
operationId: info
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: X-Owner-Token
in: header
description: Owner token returned at creation
required: true
schema:
type: string
responses:
"200":
description: Site info
content:
'*/*':
schema:
$ref: "#/components/schemas/SiteInfoResponse"
"403":
description: Missing or wrong owner token
content:
'*/*':
schema:
$ref: "#/components/schemas/SiteInfoResponse"
"404":
description: Site not found
content:
'*/*':
schema:
$ref: "#/components/schemas/SiteInfoResponse"
delete:
tags:
- Sites
summary: Delete site
description: Permanently removes the site and all its files from storage
operationId: delete_2
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: X-Owner-Token
in: header
description: Owner token returned at creation. Required for delete
required: true
schema:
type: string
responses:
"204":
description: Site deleted
"403":
description: Missing or wrong owner token
"404":
description: Site not found
/api/sites/{ns}/{id}/files/**:
get:
tags:
- Sites
summary: Serve site file
description: Serves an individual file from the site with correct content type
operationId: serveFile
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: X-Password
in: header
description: Access password via header
required: false
schema:
type: string
- name: p
in: query
description: Access password via query param
required: false
schema:
type: string
responses:
"200":
description: File content
content:
'*/*':
schema:
type: string
format: byte
"403":
description: Wrong password
content:
'*/*':
schema:
type: string
format: byte
"404":
description: Site or file not found
content:
'*/*':
schema:
type: string
format: byte
/api/sitemap.xml:
get:
tags:
- SEO
summary: Dynamic XML sitemap
description: Generates sitemap with static pages and all public gallery entries.
Caddy should route /sitemap.xml to this endpoint
operationId: sitemap
responses:
"200":
description: Sitemap XML
content:
application/xml:
schema:
type: string
/api/namespace/random:
get:
tags:
- Namespace
summary: Suggest a random namespace
description: Returns a fresh `<word>-<word>-NN` namespace from the EFF Short
Wordlist that does not collide with any existing resource. Pure read; no resource
is allocated.
operationId: random
responses:
"200":
description: Namespace suggested
content:
'*/*':
schema:
$ref: "#/components/schemas/RandomNamespaceResponse"
"503":
description: Namespace pool exhausted — retry budget hit consecutive collisions
content:
'*/*':
schema:
$ref: "#/components/schemas/RandomNamespaceResponse"
/api/kv/{ns}/{id}:
get:
tags:
- KV
summary: List keys
description: Returns all key names and total count for a store
operationId: listKeys
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: X-Password
in: header
description: Access password via header
required: false
schema:
type: string
- name: p
in: query
description: Access password via query param (alternative to X-Password header)
required: false
schema:
type: string
responses:
"200":
description: Key list
content:
'*/*':
schema:
$ref: "#/components/schemas/KvListKeysResponse"
"403":
description: Wrong password
content:
'*/*':
schema:
$ref: "#/components/schemas/KvListKeysResponse"
"404":
description: Store not found or expired
content:
'*/*':
schema:
$ref: "#/components/schemas/KvListKeysResponse"
delete:
tags:
- KV
summary: Delete entire KV bucket (all keys under ns/id)
description: Permanently removes all keys in the store; requires the owner token
returned at creation
operationId: deleteBucket
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: X-Owner-Token
in: header
description: Owner token returned at creation. Required for delete
required: true
schema:
type: string
responses:
"204":
description: Bucket deleted
"403":
description: Missing or wrong owner token
"404":
description: Store not found or expired
/api/gallery:
get:
tags:
- Gallery
summary: Browse gallery
description: "Lists public pages (public namespace, no password) with optional\
\ case-insensitive search by title/tags. When `mine=true` and `X-Owner-Token`\
\ is supplied, results are restricted to the caller's own public publications."
operationId: getGallery
parameters:
- name: q
in: query
description: Case-insensitive search by title or tags
required: false
schema:
type: string
- name: page
in: query
description: Page number (1-based)
required: false
schema:
type: integer
format: int32
default: 1
example: 1
- name: size
in: query
description: Items per page (max 100)
required: false
schema:
type: integer
format: int32
default: 20
example: 20
- name: sort
in: query
description: "Sort order: 'date' (newest first, default) or 'views' (most\
\ viewed first)"
required: false
schema:
type: string
default: date
example: date
- name: mine
in: query
description: "When true, restrict results to the caller's owner_id (requires\
\ X-Owner-Token)"
required: false
schema:
type: boolean
- name: X-Owner-Token
in: header
description: Owner token; required when mine=true
required: false
schema:
type: string
responses:
"200":
description: Paginated gallery items
content:
'*/*':
schema:
$ref: "#/components/schemas/GalleryResponse"
/api/files/{ns}/{id}:
get:
tags:
- Files
summary: Download file
description: "Returns file inline for previewable types (images, PDF, media)\
\ or as attachment. Add ?dl=1 to force download."
operationId: download
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: X-Password
in: header
description: Access password via header
required: false
schema:
type: string
- name: p
in: query
description: Access password via query param (alternative to X-Password header)
required: false
schema:
type: string
- name: dl
in: query
description: Force download as attachment
required: false
schema:
type: boolean
- name: Range
in: header
required: false
schema:
type: string
responses:
"200":
description: File content
content:
'*/*':
schema:
type: object
"403":
description: Wrong password
content:
'*/*':
schema:
type: object
"404":
description: File not found or expired
content:
'*/*':
schema:
type: object
delete:
tags:
- Files
summary: Delete file
description: Permanently removes the file from storage
operationId: delete_3
parameters:
- name: ns
in: path
required: true
schema:
type: string
pattern: "^[a-z0-9-]{1,32}$"
- name: id
in: path
required: true
schema:
type: string
- name: X-Owner-Token
in: header
description: Owner token returned at creation. Required for update and delete
required: true
schema:
type: string
responses:
"204":
description: File deleted
"403":
description: Missing or wrong owner token
"404":
description: File not found or expired
components:
schemas:
KvUpsertKeyRequest:
type: object
properties:
value:
type: string
description: New value for the key (max 1 MB)
KvUpsertKeyResponse:
type: object
properties:
id:
type: string
namespace:
type: string
key:
type: string
sizeBytes:
type: integer
format: int64
link:
type: string
description: Public short URL for this key
ownerLink:
type: string
description: API URL for programmatic access
JsonUpdateResponse:
type: object
properties:
id:
type: string
namespace:
type: string
link:
type: string
description: Public short URL for browser viewing
ownerLink:
type: string
description: API URL for programmatic access
sizeBytes:
type: integer
format: int64
updatedAt:
type: string
format: date-time
HtmlUpdateRequest:
type: object
properties:
content:
type: string
description: New HTML content to replace existing page
HtmlUpdateResponse:
type: object
properties:
id:
type: string
namespace:
type: string
link:
type: string
description: Public short URL for browser viewing
ownerLink:
type: string
description: API URL for programmatic access
expiresAt:
type: string
format: date-time
sizeBytes:
type: integer
format: int64
SiteUploadResponse:
type: object
properties:
id:
type: string
description: Unique 10-character alphanumeric site ID
namespace:
type: string
entryFile:
type: string
description: Entry HTML file resolved from the upload (e.g. index.html)
link:
type: string
description: Public URL to view the site
ownerLink:
type: string
description: "API URL for programmatic access (info, delete)"
fileCount:
type: integer
format: int32
totalSizeBytes:
type: integer
format: int64
expiresAt:
type: string
format: date-time
tags:
type: array
items:
type: string
ownerToken:
type: string
description: Secret token required for info and delete operations. Store
it safely -- cannot be recovered
ReportRequest:
type: object
properties:
reportedUrl:
type: string
description: Full URL of the reported resource on brewpage.app or brewdata.app
category:
type: string
description: Abuse category
enum:
- cannot_delete
- spam
- phishing
- malware
- copyright
- harassment
- illegal
- other
description:
type: string
description: Reporter description (10..5000 chars)
reporterEmail:
type: string
description: Optional reporter email for follow-up
resourceNamespace:
type: string
description: Optional namespace of the reported resource (e.g. 'public').
When supplied with resourceId takes priority over URL parsing.
resourceId:
type: string
description: Optional 10-character short ID of the reported resource. When
supplied with resourceNamespace takes priority over URL parsing.
ReportResponse:
type: object
properties:
reportId:
type: string
description: Unique 10-character alphanumeric report ID
receivedAt:
type: string
format: date-time
description: Server timestamp when the report was accepted
KvCreateStoreRequest:
type: object
properties:
key:
type: string
description: Initial key name
value:
type: string
description: Value for the initial key (max 1 MB)
KvCreateStoreResponse:
type: object
properties:
id:
type: string
description: Unique 10-character alphanumeric store ID
namespace:
type: string
key:
type: string
sizeBytes:
type: integer
format: int64
link:
type: string
description: Public short URL for the initial key
ownerLink:
type: string
description: API URL for programmatic access (upsert/delete)
expiresAt:
type: string
format: date-time
tags:
type: array
items:
type: string
ownerToken:
type: string
description: Secret token required for upsert and delete operations. Store
it safely -- cannot be recovered
JsonCreateResponse:
type: object
properties:
id:
type: string
description: Unique 10-character alphanumeric document ID
namespace:
type: string
link:
type: string
description: Public short URL for browser viewing
ownerLink:
type: string
description: API URL for programmatic access (update/delete)
sizeBytes:
type: integer
format: int64
expiresAt:
type: string
format: date-time
createdAt:
type: string
format: date-time
tags:
type: array
items:
type: string
ownerToken:
type: string
description: Secret token required for update and delete operations. Store
it safely -- cannot be recovered
HtmlUploadRequest:
type: object
properties:
content:
type: string
description: HTML or markdown content to publish
filename:
type: string
description: "Optional original filename used as the tab title fallback\
\ and download basename. Trimmed; rejected if it contains path separators\
\ or control characters, length > 200, or bare name shorter than 4 chars"
showTopBar:
type: boolean
description: Per-content toggle for the frontend top toolbar. null = use
global default (app.ui.show-top-bar-default)
HtmlUploadResponse:
type: object
properties:
id:
type: string
description: Unique 10-character alphanumeric page ID
namespace:
type: string
link:
type: string
description: Public short URL for browser viewing
ownerLink:
type: string
description: API URL for programmatic access (update/delete)
expiresAt:
type: string
format: date-time
sizeBytes:
type: integer
format: int64
tags:
type: array
items:
type: string
ownerToken:
type: string
description: Secret token required for update and delete operations. Store
it safely -- cannot be recovered
FileUploadResponse:
type: object
properties:
id:
type: string
description: Unique 10-character alphanumeric file ID
namespace:
type: string
filename:
type: string
description: Original filename as uploaded
contentType:
type: string
description: Detected MIME type (e.g. image/png)
sizeBytes:
type: integer
format: int64
link:
type: string
description: Public short URL for browser download
ownerLink:
type: string
description: API URL for programmatic access (delete)
expiresAt:
type: string
format: date-time
tags:
type: array
items:
type: string
ownerToken:
type: string
description: Secret token required for delete operation. Store it safely
-- cannot be recovered
OwnerCheckResponse:
type: object
properties:
type:
type: string
owner:
type: boolean
StatsResponse:
type: object
properties:
createdToday:
type: integer
format: int64
description: Resources created in the last 24 hours
totalCreated:
type: integer
format: int64
description: All-time resource count
viewsToday:
type: integer
format: int64
description: Views in the last 24 hours
totalViews:
type: integer
format: int64
description: All-time view count
breakdown:
type: array
description: "Per-type breakdown (html, json, kv, file, site)"
items:
$ref: "#/components/schemas/TypeBreakdown"
createdTodayPublic:
type: integer
format: int64
description: Resources created today in the public namespace
createdTodayPrivate:
type: integer
format: int64
description: Resources created today in private namespaces
viewsTodayPublic:
type: integer
format: int64
description: Views today on resources in the public namespace
viewsTodayPrivate:
type: integer
format: int64
description: Views today on resources in private namespaces
totalCreatedPublic:
type: integer
format: int64
description: All-time resources created in the public namespace
totalCreatedPrivate:
type: integer
format: int64
description: All-time resources created in private namespaces
totalViewsPublic:
type: integer
format: int64
description: All-time views on resources in the public namespace
totalViewsPrivate:
type: integer
format: int64
description: All-time views on resources in private namespaces
deletedToday:
type: integer
format: int64
description: Resources deleted in the last 24 hours
deletedTodayPublic:
type: integer
format: int64
description: Resources deleted today in the public namespace
deletedTodayPrivate:
type: integer
format: int64
description: Resources deleted today in private namespaces
totalDeleted:
type: integer
format: int64
description: All-time deleted resource count
totalDeletedPublic:
type: integer
format: int64
description: All-time deleted resources in the public namespace
totalDeletedPrivate:
type: integer
format: int64
description: All-time deleted resources in private namespaces
TypeBreakdown:
type: object
properties:
type:
type: string
description: "Resource type: html, json, kv, file, or site"
today:
type: integer
format: int64
description: Created in the last 24 hours
total:
type: integer
format: int64
description: All-time count
todayPublic:
type: integer
format: int64
description: Created today in the public namespace
todayPrivate:
type: integer
format: int64
description: Created today in private namespaces
totalPublic:
type: integer
format: int64
description: All-time count in the public namespace
totalPrivate:
type: integer
format: int64
description: All-time count in private namespaces
todayDeleted:
type: integer
format: int64
description: Deleted in the last 24 hours
todayDeletedPublic:
type: integer
format: int64
description: Deleted today in the public namespace
todayDeletedPrivate:
type: integer
format: int64
description: Deleted today in private namespaces
totalDeleted:
type: integer
format: int64
description: All-time deleted count
totalDeletedPublic:
type: integer
format: int64
description: All-time deleted in the public namespace
totalDeletedPrivate:
type: integer
format: int64
description: All-time deleted in private namespaces
SiteFileInfo:
type: object
properties:
path:
type: string
description: Relative file path within the site
contentType:
type: string
description: Detected MIME type
sizeBytes:
type: integer
format: int64
SiteInfoResponse:
type: object
properties:
id:
type: string
description: Unique 10-character alphanumeric site ID
namespace:
type: string
entryFile:
type: string
description: Entry HTML file resolved from the upload
fileCount:
type: integer
format: int32
totalSizeBytes:
type: integer
format: int64
files:
type: array
description: List of all files in the site
items:
$ref: "#/components/schemas/SiteFileInfo"
createdAt:
type: string
format: date-time
expiresAt:
type: string
format: date-time
views:
type: integer
format: int64
tags:
type: array
items:
type: string
RandomNamespaceResponse:
type: object
properties:
namespace:
type: string
KvStoreListResponse:
type: object
properties:
id:
type: string
description: Unique 10-character alphanumeric store ID
keyCount:
type: integer
format: int32
description: Number of keys in the store
createdAt:
type: string
format: date-time
description: When the first key in the store was created
KvListKeysResponse:
type: object
properties:
keys:
type: array
description: All key names in the store
items:
type: string
count:
type: integer
format: int32
description: Total number of keys
expiresAt:
type: string
format: date-time
description: When the store expires
views:
type: integer
format: int64
description: Aggregated views across all keys in this store
KvGetResponse:
type: object
properties:
value:
type: string
updatedAt:
type: string
format: date-time
description: When the value was last written or updated
expiresAt:
type: string
format: date-time
description: When the value expires
views:
type: integer
format: int64
description: Total number of reads for this key (post-increment)
JsonListResponse:
type: object
properties:
id:
type: string
description: Unique 10-character alphanumeric document ID
content:
type: string
size:
type: integer
format: int64
createdAt:
type: string
format: date-time
GalleryItem:
type: object
properties:
id:
type: string
type:
type: string
description: "Resource type: html, json, kv, or file"
title:
type: string
description: Display title derived from content or filename
createdAt:
type: string
format: date-time
views:
type: integer
format: int64
description: Total view count
GalleryResponse:
type: object
properties:
items:
type: array
items:
$ref: "#/components/schemas/GalleryItem"
total:
type: integer
format: int64
description: Total number of matching items across all pages
page:
type: integer
format: int32
description: Current page number (1-based)
size:
type: integer
format: int32
description: Items per page
FileListResponse:
type: object
properties:
id:
type: string
description: Unique 10-character alphanumeric file ID
filename:
type: string
description: Original filename as uploaded
contentType:
type: string
description: Detected MIME type (e.g. image/png)
size:
type: integer
format: int64
link:
type: string
description: Public short URL for browser download
createdAt:
type: string
format: date-time