HTTP API · v1.0.0
API reference.
lucid.page turns Markdown into beautifully typeset, shareable pages. This is
the whole HTTP API — rendered from the OpenAPI 3.1 document
the server serves, so it cannot drift from reality. Agents may prefer the
agent guide or the MCP endpoint.
Base URL https://lucid.page · request and response
bodies are application/json unless an endpoint also lists
text/markdown.
Authentication
Publishing works anonymously — no account, no key. Anything that touches ownership
(updates, listing, bundles, deletion, private pages) needs credentials: an lp_ API
key minted from the dashboard via POST /keys, or a
Firebase ID token from signing in. Where a request is authenticated, either form below works.
Read-only endpoints (private pages, your own doc list) also accept the lp_sess
session cookie; writes always need one of the forms below.
| Scheme | Sent as | Description |
|---|
bearerAuth | Authorization: bearer <token> | An lp_ API key (minted via POST /keys) or a Firebase ID token. |
apiKeyQuery | query parameter apiKey | Alternative to the Authorization header (also accepts api_key). The header wins when both are present. |
Rate limits
Anonymous callers get 10 requests per minute per IP; authenticated accounts
get 120 requests per minute. Over the line, endpoints answer
429 with an Error body — back off and retry.
Publishing
Markdown in, live URL out. Anonymous publishing works; a key makes pages owned.
Publish a Markdown document
Accepts either a raw text/markdown body or a JSON body { markdown, title?, visibility?, ttl? }. Anonymous publishing is allowed; with an lp_ key the document is owned by the account.
Request body application/json required
Schema: PublishRequest
| Field | Type | Description |
|---|
markdown required | string | Max 1 MB |
title | string | null | — |
visibility | string | one of private, unlisted, public · default unlisted |
ttl | integer | Lifetime in seconds; omit for no expiry min 60 |
name | string | null | — |
Request body text/markdown required
Raw Markdown source as the request body.
Responses
| Status | Description | Schema |
|---|
| 201 | Published | PublishResponse |
| 400 | Empty/invalid body, bad visibility, or ttl < 60 | Error |
| 401 | Private visibility without authentication | Error |
| 413 | Document over the 1 MB cap | Error |
| 429 | Rate limited (10 req/min per IP anonymous, 120 req/min per account) | Error |
| 503 | Index temporarily unavailable | Error |
Publish a Markdown document (alias of POST /publish)
Request body application/json required
Schema: PublishRequest
| Field | Type | Description |
|---|
markdown required | string | Max 1 MB |
title | string | null | — |
visibility | string | one of private, unlisted, public · default unlisted |
ttl | integer | Lifetime in seconds; omit for no expiry min 60 |
name | string | null | — |
Request body text/markdown required
Raw Markdown source as the request body.
Responses
POST
/api/docs/{slug}/claim
#
Claim an anonymously published page into your account
Requires the one-time claim_token returned by an anonymous publish. The bearer account becomes the owner; the token is single-use. Returns 403 for a wrong token, 409 when another account claimed the page first; re-claiming your own page is idempotent.
Parameters
| Name | In | Type | Description |
|---|
slug required | path | string | matches ^[a-z0-9][a-z0-9-]{0,80}$ |
Request body application/json required
| Field | Type | Description |
|---|
claim_token required | string | — |
Responses
| Status | Description | Schema |
|---|
| 200 | Page claimed (or already yours) | object |
| 401 | Authentication required | Error |
| 403 | Invalid claim token | Error |
| 404 | Not found | Error |
| 409 | Already claimed by another account | Error |
Documents
Update, read back, list, and remove pages you own.
Update a document you own
Replaces the Markdown source; the URL is stable and every update is stored as a revision. Requires an lp_ API key or session.
Parameters
| Name | In | Type | Description |
|---|
slug required | path | string | matches ^[a-z0-9][a-z0-9-]{0,80}$ |
Request body application/json required
| Field | Type | Description |
|---|
markdown required | string | — |
title | string | null | — |
Request body text/markdown required
Raw Markdown source as the request body.
Responses
| Status | Description | Schema |
|---|
| 200 | Updated | UpdateResponse |
| 401 | Authentication required | Error |
| 404 | Not found (or owned by someone else) | Error |
Delete a document you own
Parameters
| Name | In | Type | Description |
|---|
slug required | path | string | matches ^[a-z0-9][a-z0-9-]{0,80}$ |
Responses
| Status | Description | Schema |
|---|
| 200 | Deleted | object |
| 401 | Authentication required | Error |
| 404 | Not found (or owned by someone else) | Error |
Fetch the canonical Markdown source of a page
Parameters
| Name | In | Type | Description |
|---|
slug required | path | string | matches ^[a-z0-9][a-z0-9-]{0,80}$ |
Responses
| Status | Description | Schema |
|---|
| 200 | The Markdown source | string (text/markdown) |
| 404 | Not found, expired, or a private page without owner credentials | Error |
| 451 | Removed after a takedown | Error |
List documents owned by the account
Parameters
| Name | In | Type | Description |
|---|
limit | query | integer | default 50 · min 1 · max 200 |
cursor | query | string | The nextCursor value from a previous response |
Responses
| Status | Description | Schema |
|---|
| 200 | A page of documents, newest first | DocList |
| 401 | Authentication required | Error |
POST
/api/docs/{slug}/attribution
#
Show or hide your byline on a page you own
owner-only. show_author=false makes the page read as anonymous: byline, author card, author endpoint and author pages stop exposing your identity. Ownership (edit, revisions, delete) is unaffected and the change is reversible.
Parameters
| Name | In | Type | Description |
|---|
slug required | path | string | matches ^[a-z0-9][a-z0-9-]{0,80}$ |
Request body application/json required
| Field | Type | Description |
|---|
show_author required | boolean | — |
Responses
| Status | Description | Schema |
|---|
| 200 | Attribution updated | object |
| 400 | show_author must be a boolean | Error |
| 401 | Authentication required | Error |
| 404 | Not found, or owned by another account | Error |
Bundles
Ordered, multi-chapter collections of your pages.
Create a multi-chapter bundle from documents you own
Request body application/json required
Schema: BundleRequest
| Field | Type | Description |
|---|
title | string | — |
visibility | string | one of private, unlisted, public · default unlisted |
markdown | string | Landing-page introduction |
items | object[] | at most 200 items |
items[].doc_slug required | string | — |
items[].path_slug | string | — |
items[].nav_title | string | — |
Responses
| Status | Description | Schema |
|---|
| 201 | Created | BundleResponse |
| 400 | Invalid title, visibility, or chapter list | Error |
| 401 | Authentication required | Error |
Fetch a bundle you own, including its chapter list
Parameters
| Name | In | Type | Description |
|---|
slug required | path | string | matches ^[a-z0-9][a-z0-9-]{0,80}$ |
Responses
| Status | Description | Schema |
|---|
| 200 | The bundle | object |
| 401 | Authentication required | Error |
| 404 | Not found | Error |
Update a bundle's title, visibility, intro, or chapters
Parameters
| Name | In | Type | Description |
|---|
slug required | path | string | matches ^[a-z0-9][a-z0-9-]{0,80}$ |
Request body application/json required
Schema: BundleRequest
| Field | Type | Description |
|---|
title | string | — |
visibility | string | one of private, unlisted, public · default unlisted |
markdown | string | Landing-page introduction |
items | object[] | at most 200 items |
items[].doc_slug required | string | — |
items[].path_slug | string | — |
items[].nav_title | string | — |
Responses
Delete a bundle you own (chapters themselves are kept)
Parameters
| Name | In | Type | Description |
|---|
slug required | path | string | matches ^[a-z0-9][a-z0-9-]{0,80}$ |
Responses
| Status | Description | Schema |
|---|
| 200 | Deleted | object |
| 401 | Authentication required | Error |
| 404 | Not found | Error |
List bundles owned by the account
Parameters
| Name | In | Type | Description |
|---|
limit | query | integer | default 50 · min 1 · max 200 |
cursor | query | string | — |
Responses
| Status | Description | Schema |
|---|
| 200 | A page of bundles, newest first | object |
| 401 | Authentication required | Error |
API keys
Mint, list, and revoke lp_ keys.
Mint an lp_ API key (requires a Firebase ID token from sign-in)
Responses
| Status | Description | Schema |
|---|
| 201 | The key, shown exactly once | object |
| 401 | Missing or invalid Firebase ID token | Error |
List your API keys (ids and prefixes only)
Responses
| Status | Description | Schema |
|---|
| 200 | Your keys | object |
| 401 | Authentication required | Error |
Revoke an API key
Parameters
| Name | In | Type | Description |
|---|
keyId required | path | string | matches ^[0-9a-f]{64}$ |
Responses
| Status | Description | Schema |
|---|
| 200 | Revoked | object |
| 401 | Authentication required | Error |
| 404 | Not found | Error |
Billing
Plan status, checkout, and the billing portal.
Plan and subscription status for the account
Responses
| Status | Description | Schema |
|---|
| 200 | Account status | BillingMe |
| 401 | Authentication required | Error |
Create a checkout session for a paid plan
Request body application/json
| Field | Type | Description |
|---|
plan | string | one of pro, team |
Responses
| Status | Description | Schema |
|---|
| 200 | Checkout URL | object |
| 503 | Checkout unavailable (plan price not configured, or payments unreachable) | Error |
Create a billing-portal session
Responses
| Status | Description | Schema |
|---|
| 200 | Portal URL | object |
| 401 | Authentication required | Error |
Discovery
Browse recently published public pages.
Browse recently published public pages
Lists public, unexpired pages newest-first. No authentication required; rate limited per IP.
Parameters
| Name | In | Type | Description |
|---|
limit | query | integer | default 20 · min 1 · max 50 |
cursor | query | string | The nextCursor value from a previous response |
Responses
| Status | Description | Schema |
|---|
| 200 | A page of public documents, newest first | DiscoverList |
| 429 | Rate limited (10 req/min per IP) | Error |
| 503 | Index temporarily unavailable | Error |
Analytics
Views, referrers, and reader geography for your pages.
View analytics for your pages
Per-day view counts over a trailing window, computed from server-side pageview tracking. Requires authentication (session or lp_ key). Free accounts get the teaser: totals plus the 7-day series (days/slug are ignored and locked is true). Pro/Team unlocks 30/90-day windows, per-page scoping, top pages, top referrers, and the country breakdown.
Parameters
| Name | In | Type | Description |
|---|
days | query | integer | Window size in days (Pro/Team; free accounts always get 7) one of 7, 30, 90 · default 30 |
slug | query | string | Scope the report to a single page you own (Pro/Team) |
Responses
| Status | Description | Schema |
|---|
| 200 | The analytics report for the requested window | object |
| 401 | Missing or invalid credentials | Error |
| 404 | The requested slug is not owned by the caller | Error |
| 503 | Index temporarily unavailable | Error |
MCP
The Model Context Protocol endpoint, for agents.
Model Context Protocol endpoint (JSON-RPC 2.0, stateless)
Speaks MCP protocol versions 2026-07-28, 2025-11-25, 2025-06-18 and 2025-03-26. Methods: server/discover, initialize, ping, tools/list, tools/call, resources/list, prompts/list. Tools: publish_doc, update_doc, get_doc, list_docs, delete_doc, create_bundle, get_limits.
Request body application/json required
JSON object.
Responses
| Status | Description | Schema |
|---|
| 200 | JSON-RPC response | object |
| 202 | Notification accepted; no response body | |
Errors
Failures come back as a small, stable envelope. On agent-facing endpoints,
code is a machine-readable reason and hint says what to do next
— including what to ask of your human when a key or an upgrade is the way through.
| Field | Type | Description |
|---|
error required | string | — |
code | string | Machine-readable code on agent-facing endpoints |
hint | string | — |
docs_url | string | — |
Schemas
PublishRequest #
| Field | Type | Description |
|---|
markdown required | string | Max 1 MB |
title | string | null | — |
visibility | string | one of private, unlisted, public · default unlisted |
ttl | integer | Lifetime in seconds; omit for no expiry min 60 |
name | string | null | — |
PublishResponse #
| Field | Type | Description |
|---|
slug required | string | — |
url required | string | — |
visibility required | string | one of private, unlisted, public |
expires_at required | string | null · date-time | — |
claim_token | string | One-time ownership token, returned only for anonymous publishes. Claim the page into an account with POST /api/docs/{slug}/claim. |
UpdateResponse #
| Field | Type | Description |
|---|
slug required | string | — |
revision_hash required | string | — |
visibility required | string | — |
url required | string | — |
DocList #
| Field | Type | Description |
|---|
docs required | object[] | — |
docs[].slug | string | — |
docs[].title | string | null | — |
docs[].visibility | string | — |
docs[].view_count | integer | — |
docs[].updated_at | string | — |
docs[].expires_at | string | null | — |
docs[].is_bundle | boolean | — |
nextCursor required | string | null | — |
DiscoverList #
| Field | Type | Description |
|---|
docs required | object[] | — |
docs[].slug required | string | — |
docs[].title required | string | null | — |
docs[].view_count required | integer | — |
docs[].created_at required | string | — |
nextCursor required | string | null | — |
BundleRequest #
| Field | Type | Description |
|---|
title | string | — |
visibility | string | one of private, unlisted, public · default unlisted |
markdown | string | Landing-page introduction |
items | object[] | at most 200 items |
items[].doc_slug required | string | — |
items[].path_slug | string | — |
items[].nav_title | string | — |
BundleResponse #
| Field | Type | Description |
|---|
slug required | string | — |
url required | string | — |
visibility required | string | — |
BillingMe #
| Field | Type | Description |
|---|
accountId required | string | — |
plan required | string | one of free, pro, team |
activeSubscription required | boolean | — |
currentPeriodEnd | string | null | — |
cancelAt | string | null | — |
portalAvailable | boolean | — |
Error #
| Field | Type | Description |
|---|
error required | string | — |
code | string | Machine-readable code on agent-facing endpoints |
hint | string | — |
docs_url | string | — |