Box API
Box publishes a folder of Markdown and assets as an editorial web page. Write with tools that already understand Markdown, send one request, and share the returned URL.
Get a token
Reads are public. Writes need a Bearer token:
- The deployment admin secret
BOX_ADMIN_TOKENworks for every write. - Long-lived API tokens can be created from the admin token; they are stored hashed and can be revoked.
export BOX_TOKEN="box_..."
export BOX_URL="http://127.0.0.1:8787"
Every request that changes state sends Authorization: Bearer $BOX_TOKEN.
Publish Markdown
The smallest useful request sends JSON with a title and Markdown body. Box stores it as index.md.
curl -sS -X POST "$BOX_URL/api/artifacts" \
-H "Authorization: Bearer $BOX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Q4 Project Report",
"slug": "q4-report",
"content": "# Q4 Project Report\n\nShipping **on time**.\n"
}'
{
"id": "2f7c...",
"url": "http://127.0.0.1:8787/q4-report-k3m9xd",
"short_id": "k3m9xd",
"slug": "q4-report",
"title": "Q4 Project Report",
"visibility": "public",
"expires_at": null,
"created_at": "2026-11-14T13:20:00.000Z",
"entry": "index.md",
"files": [{ "path": "index.md", "size": 412, "mime": "text/markdown; charset=utf-8" }]
}
The URL contains a six-character short id, so titles can be renamed later without breaking the link. A retired URL redirects to the current one.
Publish Markdown with assets
Use multipart form data. The path field names the entry file; each file field's name is its target path inside the artifact. A field named files falls back to the uploaded filename.
curl -sS -X POST "$BOX_URL/api/artifacts" \
-H "Authorization: Bearer $BOX_TOKEN" \
-F "title=Launch review" \
-F "slug=launch-review" \
-F "path=index.md" \
-F "index.md=@report.md" \
-F "assets/before.png=@before.png" \
-F "assets/after.png=@after.png" \
-F "data/results.csv=@results.csv"
Every relative Markdown reference resolves under the artifact:

[Download the numbers](data/results.csv)
Box serves files with correct content types, inline-safe PDFs and images, attachment disposition for archives and unknown types, nosniff, and a sandbox CSP for SVG and HTML uploads.
Publish a ZIP
Send the whole folder as a ZIP. Box extracts it in memory, rejects traversal and hidden files, checks limits, and publishes the contents. index.md is picked automatically; otherwise pass entry.
curl -sS -X POST "$BOX_URL/api/artifacts" \
-H "Authorization: Bearer $BOX_TOKEN" \
-F "title=Rendered report" \
-F "entry=index.md" \
-F "file=@artifact.zip"
ZIP limits: 40 MB archive, 200 entries, 25 MB per file, 150 MB expanded total.
Read an artifact
| URL | Returns |
|---|---|
/{slug}-{shortid} |
Rendered page |
/{slug}-{shortid}/raw |
Original Markdown entry |
/{slug}-{shortid}/download |
ZIP of the artifact |
/{slug}-{shortid}/_files/{path} |
A published asset |
/api/artifacts/{id} |
JSON metadata (Bearer) |
/api/artifacts |
JSON list (Bearer) |
curl -sS "$BOX_URL/api/artifacts" -H "Authorization: Bearer $BOX_TOKEN"
Update and delete
curl -sS -X PUT "$BOX_URL/api/artifacts/{id}" \
-H "Authorization: Bearer $BOX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Q4 Report (final)","slug":"q4-final","visibility":"private"}'
curl -sS -X DELETE "$BOX_URL/api/artifacts/{id}" \
-H "Authorization: Bearer $BOX_TOKEN"
Add or replace one asset without re-uploading the bundle:
curl -sS -X POST "$BOX_URL/api/artifacts/{id}/assets" \
-H "Authorization: Bearer $BOX_TOKEN" \
-F "assets/after.png=@after-v2.png"
Delete one asset with DELETE /api/artifacts/{id}/assets/{path}.
Private artifacts
Private artifacts return 404 to browsers without a valid view link. Create a signed, expiring link:
curl -sS -X POST "$BOX_URL/api/artifacts/{id}/share-link" \
-H "Authorization: Bearer $BOX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"ttl_seconds": 86400}'
The response contains path and query. Opening path + query sets a path-scoped, HttpOnly cookie and redirects to the clean URL. API tokens are never placed in browser URLs.
API tokens
curl -sS -X POST "$BOX_URL/api/tokens" \
-H "Authorization: Bearer $BOX_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"CI publisher"}'
The plaintext token appears once in the response. List tokens with GET /api/tokens (prefixes only); revoke with DELETE /api/tokens/{id}.
Markdown
CommonMark plus tables, footnotes, task lists, and autolinks. Raw HTML is rejected, so published Markdown cannot inject scripts. Heading ids and the table of contents are generated from ## and ###.
Figures and captions
A paragraph containing one image becomes a figure. The image title becomes the caption:

Comparisons
:::compare
before: assets/before.png
after: assets/after.png
before-label: Before
after-label: After
position: 50
caption: Latency before and after the cache.
:::
The slider works with pointer, touch, and keyboard (arrow keys, Home, End) and expands full screen.
Galleries
:::gallery
assets/one.png | First variant
assets/two.png | Second variant
assets/three.png | Third variant
:::
Callouts
:::warning
Retiring this endpoint in March.
:::
Supported: note, tip, warning, caution, details, wide.
Math and diagrams
Inline $e^{i\pi} + 1 = 0$ and display $$...$$ math render with KaTeX on the server. A mermaid fence renders as a diagram in the browser (the CDN is allowlisted by CSP).
Errors
JSON errors are predictable:
{ "error": { "code": "invalid_request", "message": "Provide at least one file." } }
| Status | Meaning |
|---|---|
| 400 | Malformed request or invalid field |
| 401 | Missing or invalid token |
| 404 | Artifact not found or expired |
| 409 | Slug or id collision |
| 413 | File, count, or size limit exceeded |
| 429 | Write rate limit |
Writes are limited per token per minute. Reads are limited more loosely. HTML pages are cached only for public artifacts, and expiration is checked against D1 before any cached response is served.