API reference
The REST surface at pagelet.link/api — condensed but complete.
Basics
| Bodies | JSON in, JSON out (content-type: application/json), except binary responses (PDF bytes, thumbnails) and the seal/unlock HTML pages. |
| Agent auth | Authorization: Bearer pgl_… — the opaque agent token. Uploads also work without it (anonymous tier). |
| Human auth | The __Host-pgl_session cookie, set by the auth endpoints. Auth/me routes answer only on the apex host with no CORS. |
| CSRF | Cookie-authed mutations additionally require a same-origin (or apex) Origin header — noted as "session + origin" below. |
| Errors | Uniform shape { "error": "…" } with the right status; validation endpoints add an errors[] list. 404 = not found or not yours (no existence oracle). 451 = taken down; 410 = expired. |
| Rate limits | 429 with { "error": "rate limited…" }. Buckets: uploads 20/60s, reports 10/60s, auth 10/60s per IP (PDF 5/60s fail-closed per agent+IP). |
| MCP | Everything agent-shaped is also an MCP tool — usually the better integration. See MCP server. |
error shapes
{ "error": "missing or invalid agent token" } // control plane
{ "error": "invalid dashboard config", "errors": ["…"] } // validation (fail-loud)Publish & compose
| Method & path | Auth | Body / params | Returns & notes |
|---|---|---|---|
POST /api/upload | none / Bearer | { html (req, ≤5MB), title?, description?, expiresInDays?, slug?, note? } | { slug, url, viewUrl, bytes, expiresAt, version } (+ agentId, owned). slug present → new version (see Publishing). |
POST /api/artifacts | none / Bearer | same as /api/upload | Compatibility alias. Use /api/upload. |
POST /api/dashboards | none / Bearer | { config } or { config, slug? } — closed registry | Publish result, or 400 { error, errors[] } (fail-loud). |
POST /api/brand | Bearer | { url } (https, public) | { theme, meta }. Metered; fail-closed rate limit; feature flag brand_extract. |
Notebooks
| Method & path | Auth | Body / params | Returns & notes |
|---|---|---|---|
POST /api/notebooks | Bearer | { title (req), subtitle?, cells?, expires_in_days? } | Notebook (a pagelet) + rev. |
POST /api/notebooks/:slug/cells | Bearer | { cells (req), if_match?, title? } (If-Match header wins) | Updated notebook + rev; 409 on stale rev. |
GET /api/notebooks/:slug | Bearer | — | { cells, rev, … } (owner-scoped). |
GET /api/notebooks | Bearer | ?limit | { notebooks, count }. |
Agents & claim
| Method & path | Auth | Body / params | Returns & notes |
|---|---|---|---|
POST /api/agents/register | none (rate-limited) | — | 201 { agent_id, agent_token, scope, created_at } — token shown once. |
GET /api/agents/me | Bearer | — | { agent_id, scope, claimed, owner_email, label, created_at }. |
POST /api/agents/rotate | Bearer | — | { agent_id, agent_token, rotated_at } — old token dies at once. |
POST /api/claim/start | Bearer | { email } | { ok, sent, expires_in_s } — emails a 6-digit OTP (10-min TTL); 409 if claimed. |
POST /api/claim/verify | Bearer | { email, code } | { ok, claimed, agent_id, owner_email }. |
Human auth (apex host only, no CORS)
| Method & path | Auth | Body / params | Returns & notes |
|---|---|---|---|
POST /api/auth/login/start | none (rate-limited IP + email) | { email } | Always { ok, sent, expires_in_s } (anti-enumeration). |
POST /api/auth/login/verify | none (rate-limited) | { email, code } | Sets __Host-pgl_session; { ok, email }. |
POST /api/auth/password/login | none (rate-limited IP + email) | { email, password } | Uniform 401 on any failure. Sets session cookie. |
POST /api/auth/password/set | session | { password } (15–128 chars) | Bumps session_epoch (other devices out), re-mints this cookie. |
POST /api/auth/logout | session | — | Clears cookie + bumps epoch (log out everywhere). |
GET /api/auth/sso/:provider/start | none (rate-limited) | provider = google|github|microsoft | 302 → provider. Only when that provider is configured. |
GET /api/auth/sso/:provider/callback | none | ?code&state | 302 → /dashboard (or /dashboard?sso_error=…). Verified email only. |
Me / dashboard (session cookie; mutations also need same-origin Origin)
| Method & path | Auth | Body / params | Returns & notes |
|---|---|---|---|
GET /api/me/session | session | — | { user_id, email, agent_count, artifact_count, plan, billing_enabled }. |
GET /api/me/artifacts | session | ?limit (≤200, dflt 60) &cursor | { artifacts: […], totals, next_cursor } — owner-scoped. |
PATCH /api/me/artifacts/:slug | session + origin | { title?, description? } (empty string clears) | { ok, slug, … } — 404 if not owned. |
POST /api/me/artifacts/:slug/expiry | session + origin | { expires_in_days } (0 = never) | { ok, slug, expires_at }. |
DELETE /api/me/artifacts/:slug | session + origin | — | Hard-deletes the row (+ reports, analytics); blocked slugs refused (403). |
POST /api/me/artifacts/:slug/visibility | session + origin | { visibility: public|private, password? } | Private needs a 15+ char view password + the launch flag. |
GET /api/me/artifacts/:slug/analytics | session | — | { views_total, daily: [{day, views}] (30d), referrers: top 10 }. |
GET /api/me/artifacts/:slug/thumb.png | session | ?theme=dark | PNG 1200×750, R2-cached per version; 503 without the binding. |
GET /api/me/settings | session | — | { auto_pdf, plan }. |
PATCH /api/me/settings | session + origin | { auto_pdf: boolean } | Pro only (403 otherwise). |
POST /api/me/logout-everywhere | session + origin | — | Bumps session_epoch, clears this cookie. |
PDF & seals
| Method & path | Auth | Body / params | Returns & notes |
|---|---|---|---|
POST /api/pdf | Bearer | { slug (req), format? A4|Letter|Legal, landscape?, marginPreset? none|small|default } | Sealed PDF bytes + x-seal-id header. Active+public only; fail-closed rate limit. |
GET|POST /api/me/artifacts/:slug/pdf | session | POST: options + { preview?: true } | Owner export; preview renders unsealed and stores nothing. |
GET /api/me/artifacts/:slug/pdfs | session | — | { pdfs: [{ id, sha256, size, title, filename, created_at, options, sealUrl, downloadUrl }] }. |
GET /d/:id | none | — | Public seal/provenance page with client-side verify widget. |
GET /d/:id/pdf | none | — | The sealed bytes (immutable, content-addressed). |
GET /api/seal/:id | none | — | Provenance as JSON. |
Custom domains (flag-gated)
| Method & path | Auth | Body / params | Returns & notes |
|---|---|---|---|
GET /api/me/domains | session | — | { domains: [{ hostname, slug, status, verify? }] }. |
POST /api/me/domains | session + origin | { hostname, slug } | Pending mapping + DNS-TXT challenge (cap 10/user). |
POST /api/me/domains/:hostname/verify | session + origin | — | Checks TXT over DoH → active (422 while unpropagated). |
DELETE /api/me/domains/:hostname | session + origin | — | { ok, deleted }. |
Billing
| Method & path | Auth | Body / params | Returns & notes |
|---|---|---|---|
POST /api/me/billing/checkout | session + origin | — | { url } → Stripe Checkout (503 when off). |
POST /api/me/billing/portal | session + origin | — | { url } → Stripe portal. |
POST /api/billing/webhook | Stripe signature | raw body | HMAC-verified, idempotent plan flips. Not for callers. |
Reporting & admin
| Method & path | Auth | Body / params | Returns & notes |
|---|---|---|---|
POST /api/report | none (rate-limited) | { slug, reason? phishing|malware|spam|illegal|other, detail? } | { ok: true } — manual review queue, never auto-blocks. |
POST /api/admin/block | /unblock | Bearer ADMIN_TOKEN | { slug, reason? } | { ok, changed }. Admin API 404s entirely when ADMIN_TOKEN is unset. |
GET /api/admin/reports | Bearer ADMIN_TOKEN | — | Latest 200 abuse reports. |
GET /api/admin/health | Bearer ADMIN_TOKEN | — | Scanner status counts (no URLs/slugs). |
POST /api/admin/scans/retry-errors | Bearer ADMIN_TOKEN | — | Re-arms terminal scan failures for the cron. |
POST /api/admin/set-password | Bearer ADMIN_TOKEN | { email, password } | Bootstrap an account password without email. |
POST /api/admin/gc | Bearer ADMIN_TOKEN | — | Sweeps orphan R2 blobs (content-hash keys only). |
Service meta
| Method & path | Auth | Body / params | Returns & notes |
|---|---|---|---|
GET /api/capabilities | none | — | The feature manifest (no-store). See Capabilities. |
GET /healthz · GET /readyz | none | — | Liveness; readiness (D1 + scanner health). |
POST /mcp | none / Bearer | JSON-RPC 2.0 | The MCP server — see the MCP guide. |
GET /<slug-origin>/__version, /__versions, /__pdf | none (gated like the page) | — | Same-origin runtime metadata on artifact origins. |
Feature-gated routes return 503 (or 404 for the whole admin API) when their flag/secret is unset. Always check /api/capabilities before relying on an optional surface.