Fail closed, politely
Error shapes
The boring failures your agent should expect.
Invalid or missing team-auth fails closed with 401:
{"detail":"Invalid Authorization header"}
Duplicate document slugs are team-scoped and return 409:
{"detail":"Document slug already exists for this team"}
Version bodies must be raw UTF-8 text. Invalid bytes return 400:
{"detail":"Version body must be valid UTF-8"}
Theme logo uploads are intentionally narrow. Allowed content types are
image/png, image/jpeg, image/gif, and image/webp; the bytes must match
the declared type and be at most 256 KiB. Bad logo input returns 400, for
example:
{"detail":"Logo content_type must be image/png, image/jpeg, image/gif, or image/webp"}
Present-link mint/revoke is scoped to the authenticated certificate’s team. Missing documents, cross-team attempts, unknown tokens, expired tokens, and revoked tokens return 404 without an existence signal:
{"detail":"Presentation not found"}
Free-tier cap writes fail with structured 402. Reads, version history, and existing links continue to work:
{
"detail": {
"code": "free_tier_limit_exceeded",
"limit": "documents",
"current": 3,
"max": 3,
"subscriptions_available": false,
"message": "Free tier limit reached; subscriptions are not yet available."
}
}
Request-shape errors (bad slug, missing slug, bad version, malformed theme
JSON) return FastAPI/Pydantic 422 details.
Stripe checkout, portal, and webhook endpoints are v2 scope and are not
available yet. In v1, agents can read status with GET /v1/billing; paying is a
future browser moment for the human.