{"openapi":"3.0.3","info":{"title":"Verso API","version":"1.0.0","description":"The public REST API over one surface of one site. Authenticate with a bearer token minted in Settings → API; every token is scoped to a single surface, and every request resolves through the same permission model as the reader — an unreadable surface answers 404, never 403. Writes open proposals; publishing is a separate act with a separate capability."},"servers":[{"url":"/"}],"security":[{"bearer":[]}],"paths":{"/api/v1/pages":{"get":{"summary":"List the pages of a surface","description":"Requires `read`. Answers every page on the surface the credential resolves to.","parameters":[{"name":"site","in":"query","required":false,"schema":{"type":"string"},"description":"Site slug. Explicit addressing for machine callers; omitted, the host decides."},{"name":"surface","in":"query","required":false,"schema":{"type":"string"},"description":"Surface key (audience). A bearer token is minted for exactly one surface; naming any other grants nothing."}],"responses":{"200":{"description":"The pages, in navigation order.","content":{"application/json":{"schema":{"type":"object","properties":{"pages":{"type":"array","items":{"$ref":"#/components/schemas/Page"}}}}}}},"404":{"description":"No such surface, or no `read` on it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a page (as a proposal)","description":"Requires `propose`. Opens a proposal containing the new page and answers its id — nothing is live until the proposal publishes.","parameters":[{"name":"site","in":"query","required":false,"schema":{"type":"string"},"description":"Site slug. Explicit addressing for machine callers; omitted, the host decides."},{"name":"surface","in":"query","required":false,"schema":{"type":"string"},"description":"Surface key (audience). A bearer token is minted for exactly one surface; naming any other grants nothing."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePage"}}}},"responses":{"201":{"description":"The proposal now carrying the page.","content":{"application/json":{"schema":{"type":"object","properties":{"proposalId":{"type":"string"},"pageId":{"type":"string"},"path":{"type":"string"},"slug":{"type":"string"}}}}}},"400":{"description":"Missing title, or an address the product reserves.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A page already lives at that address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/pages/{slug}":{"get":{"summary":"Read one page, record and source","description":"Requires `read`. `slug` is the reader’s address, e.g. `guides/refunds`. Answers the indexed record and the exact MDX at the surface’s ref.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"Path-style slug; slashes allowed."},{"name":"site","in":"query","required":false,"schema":{"type":"string"},"description":"Site slug. Explicit addressing for machine callers; omitted, the host decides."},{"name":"surface","in":"query","required":false,"schema":{"type":"string"},"description":"Surface key (audience). A bearer token is minted for exactly one surface; naming any other grants nothing."}],"responses":{"200":{"description":"The page.","content":{"application/json":{"schema":{"type":"object","properties":{"page":{"$ref":"#/components/schemas/Page"},"source":{"type":"string","description":"The complete MDX file, frontmatter included."}}}}}},"404":{"description":"No page there, or nothing you may read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Replace a page’s source (as a proposal)","description":"Requires `propose`. Send back the edited `source` a GET returned.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"site","in":"query","required":false,"schema":{"type":"string"},"description":"Site slug. Explicit addressing for machine callers; omitted, the host decides."},{"name":"surface","in":"query","required":false,"schema":{"type":"string"},"description":"Surface key (audience). A bearer token is minted for exactly one surface; naming any other grants nothing."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["source"],"properties":{"source":{"type":"string","description":"The complete MDX file to store."}}}}}},"responses":{"200":{"description":"The proposal carrying the edit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProposalRef"}}}},"404":{"description":"No page there.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Remove a page (as a proposal)","description":"Requires `propose`. The removal is a proposal like any other change.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"site","in":"query","required":false,"schema":{"type":"string"},"description":"Site slug. Explicit addressing for machine callers; omitted, the host decides."},{"name":"surface","in":"query","required":false,"schema":{"type":"string"},"description":"Surface key (audience). A bearer token is minted for exactly one surface; naming any other grants nothing."}],"responses":{"200":{"description":"The proposal carrying the removal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProposalRef"}}}},"404":{"description":"No page there.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/proposals":{"get":{"summary":"List proposals on a surface","description":"Requires `read`. Only this surface’s proposals — another audience’s drafts are absent, not refused. Filter with `state=open|merged|conflicted|closed`.","parameters":[{"name":"site","in":"query","required":false,"schema":{"type":"string"},"description":"Site slug. Explicit addressing for machine callers; omitted, the host decides."},{"name":"surface","in":"query","required":false,"schema":{"type":"string"},"description":"Surface key (audience). A bearer token is minted for exactly one surface; naming any other grants nothing."},{"name":"state","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The proposals, most recently updated first.","content":{"application/json":{"schema":{"type":"object","properties":{"proposals":{"type":"array","items":{"$ref":"#/components/schemas/Proposal"}}}}}}}}},"post":{"summary":"Open a proposal","description":"Requires `propose`. `files` maps repo-relative paths to full content, or null to remove a path.","parameters":[{"name":"site","in":"query","required":false,"schema":{"type":"string"},"description":"Site slug. Explicit addressing for machine callers; omitted, the host decides."},{"name":"surface","in":"query","required":false,"schema":{"type":"string"},"description":"Surface key (audience). A bearer token is minted for exactly one surface; naming any other grants nothing."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProposal"}}}},"responses":{"201":{"description":"Opened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProposalRef"}}}},"400":{"description":"No title, no files, or a path that is not repo-relative.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/proposals/{id}":{"get":{"summary":"Read one proposal and its ledger","description":"Requires `read` on the surface the proposal itself names — the caller’s site and surface parameters get no vote.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The proposal and a consequence-ranked summary of its changes.","content":{"application/json":{"schema":{"type":"object","properties":{"proposal":{"$ref":"#/components/schemas/Proposal"},"ledger":{"$ref":"#/components/schemas/LedgerSummary"}}}}}},"404":{"description":"No such proposal you may see.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/proposals/{id}/publish":{"post":{"summary":"Publish a proposal","description":"Requires `merge`, and an accountable person: the session’s user, or the user who minted the token. Records an approval covering the whole ledger in the publisher’s name, then squash-merges — the same record every other publish leaves.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Live.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"sha":{"type":"string"}}}}}},"403":{"description":"No `merge`, or no accountable person behind the credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already published, or it conflicts with Live.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/search":{"get":{"summary":"Search a surface","description":"Requires `search`. Hybrid keyword + vector over this surface’s own physical index partition; `mode=keyword` skips vectors.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"1–50, default 10."},{"name":"mode","in":"query","required":false,"schema":{"type":"string"},"description":"'hybrid' (default) or 'keyword'."},{"name":"site","in":"query","required":false,"schema":{"type":"string"},"description":"Site slug. Explicit addressing for machine callers; omitted, the host decides."},{"name":"surface","in":"query","required":false,"schema":{"type":"string"},"description":"Surface key (audience). A bearer token is minted for exactly one surface; naming any other grants nothing."}],"responses":{"200":{"description":"Ranked results with block-level citations."},"429":{"description":"Rate limited; honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/events":{"get":{"summary":"List a surface’s events","description":"Requires `read`. Newest first; events with no surface (site-wide acts) are absent from this feed by construction. Page backwards with `before=<id>`.","parameters":[{"name":"site","in":"query","required":false,"schema":{"type":"string"},"description":"Site slug. Explicit addressing for machine callers; omitted, the host decides."},{"name":"surface","in":"query","required":false,"schema":{"type":"string"},"description":"Surface key (audience). A bearer token is minted for exactly one surface; naming any other grants nothing."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"1–200, default 50."},{"name":"before","in":"query","required":false,"schema":{"type":"string"},"description":"An event id; answers events strictly older."},{"name":"type","in":"query","required":false,"schema":{"type":"string"},"description":"e.g. 'page.published'."}],"responses":{"200":{"description":"The events.","content":{"application/json":{"schema":{"type":"object","properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"nextBefore":{"type":"string","description":"Cursor for the next page, or null."}}}}}}}}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"An API token (`vs_…`) minted in Settings → API. Scoped to one surface; carries capabilities, not a role."}},"schemas":{"Error":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string","description":"A sentence a person can act on."},"code":{"type":"string","description":"'not_found' | 'forbidden' | 'bad_request' | 'conflict'"}}},"Page":{"type":"object","required":["id","slug","title","path"],"properties":{"id":{"type":"string","description":"Stable page id; survives moves and renames."},"slug":{"type":"string","description":"The reader’s address, e.g. /guides/refunds."},"title":{"type":"string"},"description":{"type":"string"},"section":{"type":"string","description":"Parent slug, or null at the top level."},"tags":{"type":"array","items":{"type":"string"}},"path":{"type":"string","description":"Repo-relative file path."},"frontmatter":{"type":"object","description":"The page’s own frontmatter, verbatim."}}},"CreatePage":{"type":"object","required":["title"],"properties":{"title":{"type":"string"},"section":{"type":"string","description":"Section slug, e.g. guides. Top level when absent."},"content":{"type":"string","description":"MDX body. Defaults to the title as a heading."}}},"Proposal":{"type":"object","required":["id","number","title","state"],"properties":{"id":{"type":"string"},"number":{"type":"integer"},"title":{"type":"string"},"body":{"type":"string"},"state":{"type":"string","description":"'open' | 'merged' | 'conflicted' | 'closed'"},"origin":{"type":"string","description":"'app' | 'api' | 'git_push' | 'import' | 'ai'"},"author":{"type":"string"},"baseRef":{"type":"string"},"baseSha":{"type":"string"},"headSha":{"type":"string"},"mergeCommitSha":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"mergedAt":{"type":"string"}}},"CreateProposal":{"type":"object","required":["title","files"],"properties":{"title":{"type":"string"},"body":{"type":"string"},"files":{"type":"object","description":"Repo-relative path → full file content, or null to remove that path."}}},"ProposalRef":{"type":"object","required":["proposalId"],"properties":{"proposalId":{"type":"string"},"path":{"type":"string"}}},"LedgerSummary":{"type":"object","properties":{"total":{"type":"integer"},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"group":{"type":"string"},"consequences":{"type":"array","items":{"type":"string"}},"reason":{"type":"string","description":"Why this change was flagged, in plain language."},"page":{"type":"string"},"path":{"type":"string"}}}}}},"Event":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","description":"ULID — lexicographic order is time order."},"type":{"type":"string"},"subject":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}}},"payload":{"type":"object"},"occurredAt":{"type":"string"}}}}}}