Skip to content

HTTP API ​

This page is generated by docs/gen-api.py from the server's OpenAPI. Each description is the first sentence of the endpoint function's docstring.

Version

This page follows the server on the main branch. The Docker image latest (v0.1.0) is older and lacks the endpoints added since, such as /api/todos/{todo_id}/mark and /api/lang.

Authentication ​

  • Every request except GET /api/health needs a tenant's token: the header Authorization: Bearer <token>, or the cookie token.
  • Open any GET page once with ?token=<token>: the server sets a cookie valid for a year and redirects (303) to the address without the token.
  • Without a valid token, HTTP answers 401 and a WebSocket is closed with 4401.
  • When tenants.json has a single tenant whose token is empty, there is no authentication.
  • The firmware writes (POST /api/firmware, PATCH /api/firmware/{model}/{version}) also need X-Firmware-Token: <FIRMWARE_TOKEN>.

Not in the tables ​

  • Bodies of raw bytes, which OpenAPI does not describe: the body of POST /api/chunks is one whole audio file, the body of POST /api/firmware a firmware package.
  • GET /ws: a WebSocket on which the server pushes the tenant's updates to the web pages.
  • The web app's own routes (/, /live, /conversations and so on) and /assets/*: see Web pages and app settings.

Endpoints ​

Parameters: name? is optional; the parentheses give where it goes (query, path) and its type.

/api/firmware ​

MethodDescriptionParameters
POSTOne DFU package (lr.sh pkg) as the body; the model is the build's own (CONFIG_BT_DIS_MODEL), read by lr_release.py from the build directory the package came from.model (query, string)
version (query, string)
commit (query, string)
built (query, string)
notes (query, string)
GETA model's packages, newest first: the app's firmware page lists them with their notes.model (query, string)

/api/firmware/{model}/{version} ​

MethodDescriptionParameters
PATCHNew notes for a published version (lr_release.py --version); the package stays as it is.model (path, string)
version (path, string)
notes (query, string)

/api/firmware/latest ​

MethodDescriptionParameters
GETThe newest package of a model (what the app compares with its recorder's revision), or 404.model (query, string)

/api/firmware/{model}/{version}.zip ​

MethodDescriptionParameters
GET(no docstring)model (path, string)
version (path, string)

/api/addresses ​

MethodDescriptionParameters
GET(no docstring)—

/app.apk ​

MethodDescriptionParameters
GETThe Android client, for installing from the phone's browser.—

/api/app/latest ​

MethodDescriptionParameters
GETWhat /app.apk is: the Android app compares versionCode with its own build number and offers the update.—

/api/chunks ​

MethodDescriptionParameters
POSTOne self-contained audio file (the apps' Opus or AAC pieces), start its device time in unix ms, n the 16 kHz samples the client wrote into it (the iPhone's AAC: the decoder adds some at both ends).device (query, string)
start (query, integer)
n? (query, integer)
hold? (query, integer)

/api/chunks/end ​

MethodDescriptionParameters
POSTThe recorder stopped at device time t (unix ms): the open conversation closes once its audio is processed.device (query, string)
t (query, integer)

/api/hold/release ​

MethodDescriptionParameters
POSTThe phone no longer has the recorder (unbound, or another bound in its place): the older audio on its card will not come from it, so finalization stops waiting for it now, not HOLD_MAX_S after the phone last said the hold.device (query, string)

/api/device-status ​

MethodDescriptionParameters
POSTWhat a client read from a BLE recorder (battery, card, firmware, model): each one kept, the newest shown with the device's pipeline.device (query, string)
body: JSON object

/api/device-log ​

MethodDescriptionParameters
POSTA batch of a device's events, from the phone that read them; one sent again replaces the one kept.device (query, string)
body: JSON DeviceEvent[] {src, seq, at, data}
GETA device's events, status reports and audio by the minute (audio.minutes) in [since, until).device (query, string)
since (query, string)
until? (query, string)

/api/calls ​

MethodDescriptionParameters
GETThe ASR and LLM calls since since, by kind and engine (db.calls), and the StepFun balance (db.balance).since (query, string)

/api/perf ​

MethodDescriptionParameters
GETA day's (YYYY-MM-DD) performance rows in this tenant's store, oldest first (perf.day): {stalls, samples, requests, pieces}; the event loop's stalls and the process's minutes are in the first tenant's only.day (query, string)

/api/upgrades ​

MethodDescriptionParameters
GETA device's firmware updates begun since since, oldest first: what its app logged joined with the recorder's DFU and BOOT records (db.upgrades).device (query, string)
since (query, string)

/api/client-config ​

MethodDescriptionParameters
GETWhat the apps ask on start and while recording: piece lengths per app state (the server changes them without a new build; short pieces in front make drafts quick, long ones in the background save requests).—

/api/app-setup ​

MethodDescriptionParameters
GETFor the web page's QR: the token to hand the app (its tenant's, the one the page sent), so it doesn't need typing in by hand.—

/api/events ​

MethodDescriptionParameters
GET(no docstring)—

/api/live ​

MethodDescriptionParameters
GETEach device's live analysis of its open conversation.—

/api/live/{device} ​

MethodDescriptionParameters
POST立即总结 (the live page's button): the device's live analysis now; 409 with why not.device (path, string)

/api/health ​

MethodDescriptionParameters
GETNo token and no tenant (Tenants lets it through): for reverse proxies and docker-compose.yml's healthcheck.—

/api/status ​

MethodDescriptionParameters
GET(no docstring)—

/api/lang ​

MethodDescriptionParameters
GETWhat the models write this tenant's summaries, recaps and answers in (tenant.py): {"lang": "zh"} or "en".—
PUTThe language from the next model call on; existing summaries keep theirs (重新生成 rewrites one).body: JSON object

/api/sentences ​

MethodDescriptionParameters
GET(no docstring)date? (query, string)

/api/conversations ​

MethodDescriptionParameters
GET(no docstring)date? (query, string)

/api/days ​

MethodDescriptionParameters
GETDays that have sentences, newest first.—

/api/calendar ​

MethodDescriptionParameters
GETPer day with sentences: closed conversations, their speech minutes and the sentence count, for the day picker and the calendar page.—
MethodDescriptionParameters
GETdb.search; before is the start of the last one already shown.q? (query, string)
tag? (query, string)
who? (query, string)
after? (query, string)
before? (query, string)
limit? (query, integer)

/api/ask ​

MethodDescriptionParameters
POSTA question about my own past answered from this app's data (ask.py), in 10-30 s: {answer, sources, calls, usage}.body: JSON object

/api/ask/voice ​

MethodDescriptionParameters
POSTA question said aloud (the ask sheet's hold-to-talk: what the browser's MediaRecorder wrote, webm, ogg or mp4) as text, {text}, for the page to show and ask with /api/ask; "" when nothing was heard.—

/mcp ​

MethodDescriptionParameters
GETNo server-sent stream and no sessions to end (2026-07-28).—
POSTRemote MCP for the owner's agents (mcp_server.py), with the same token as every other client: stateless, JSON only.—
DELETENo server-sent stream and no sessions to end (2026-07-28).—

/api/recap ​

MethodDescriptionParameters
GETA day's recap, the newest one when no day is given; null when there is none.date? (query, string)

/api/todos/due ​

MethodDescriptionParameters
GETMy todos due today or before, not marked done or wrong, with how stale each is (db.due_todos).—

/api/todos/{todo_id}/mark ​

MethodDescriptionParameters
PUT完成 (done) or 不对 (wrong) on one todo, by its id (db.todo_ids); a mark again replaces it.todo_id (path, string)
body: JSON object
DELETEThe undo: the todo is open again.todo_id (path, string)

/api/todos/marks ​

MethodDescriptionParameters
POSTMany todos at once: {ids, mark}, mark done or wrong, or null to drop their marks (the bulk undo).body: JSON object

/api/recap/{date} ​

MethodDescriptionParameters
POSTWrite a day's recap again (15-20 s), e.g. after its summaries were redone.date (path, string)

/api/tags ​

MethodDescriptionParameters
GET(no docstring)—

/api/conversation/{conv_id} ​

MethodDescriptionParameters
GET(no docstring)conv_id (path, string)

/api/conversation/{conv_id}/feedback ​

MethodDescriptionParameters
POSTA correction in the owner's words (wrongly greyed as media, a wrong speaker, a bad summary); collected and read back in batches to fix the prompt, never acted on one by one.conv_id (path, string)
body: JSON object

/api/feedback ​

MethodDescriptionParameters
GETEvery correction, newest first, with the conversation's time and title for the read-back.—

/api/conversation/{conv_id}/summarize ​

MethodDescriptionParameters
POST(no docstring)conv_id (path, string)

/api/names ​

MethodDescriptionParameters
GETNames already in use, most conversations first: suggestions for a rename.—

/api/conversation/{conv_id}/rename ​

MethodDescriptionParameters
POSTOne label renamed; with a name the conversation has already, a merge; to 我, its voice learnt too (learn_me).conv_id (path, string)
body: JSON object

/api/speakers/undo ​

MethodDescriptionParameters
POSTTakes back a rename, merge or 拆开 by the undo id its answer gave: the rows, the participants, the voices, pins and name hints as they were, the summaries redone, what the renamed sentences taught the people bank (the ledger, bank.py) both ways, the centroid a 「这是我」 added to 我's voiceprint dropped. 409 when the id is unknown (taken back already, too old, the server restarted) or a conversation changed since.body: JSON object

/api/speakers ​

MethodDescriptionParameters
GETThe speakers page's cards of people (named, or OTHERS_MIN_S of speech over OTHERS_MIN_CONVS conversations) and how many other voices there are; ?others=1 the cards of those.others? (query, boolean)

/api/speakers/{gid} ​

MethodDescriptionParameters
GET(no docstring)gid (path, string)

/api/speakers/{gid}/halves ​

MethodDescriptionParameters
GETThe two-half test of a card (bank.halves): mixed, and its two halves when it sounds like two people.gid (path, string)

/api/speakers/name ​

MethodDescriptionParameters
POSTEvery member voice of a group (or of one of its suggestions) takes the name, in all of its conversations; with voices (conv|label), only those.body: JSON object

/api/speakers/detach ​

MethodDescriptionParameters
POSTA voice that is not this speaker: a named one goes back to a fresh S<n> of its conversation; either way it is kept apart from the group from now on.body: JSON object

/api/speakers/{gid}/split ​

MethodDescriptionParameters
POST拆开: the voices of a card a request picks (chosen: a half from the two-half test) out of it into a card of their own, in one transaction, with an undo.gid (path, string)
body: JSON object

/api/conversation/{conv_id}/speakers ​

MethodDescriptionParameters
GET(no docstring)conv_id (path, string)

/api/aliases ​

MethodDescriptionParameters
GET(no docstring)—
POSTAnother way a person is called (A哥 for A): hints with it suggest the name.body: JSON object

/api/person/{name} ​

MethodDescriptionParameters
GETEverything about one named speaker: their conversations newest first with what they said there, the todos they own, and what I promised them.name (path, string)

/api/person/{name}/profile ​

MethodDescriptionParameters
POSTMade (or made again) now: one chat call, some seconds.name (path, string)

/api/voice-clip/{name} ​

MethodDescriptionParameters
GET(no docstring)name (path, string)

/api/self ​

MethodDescriptionParameters
GETThe card while it is up: candidates most likely first (none preselected), each with a tier instead of a %.—
POSTThe answer to the card: a candidate's index becomes 我's voiceprint (owner-redimnet2.npz), taken by every pipeline from its next conversation on: the conversations before keep their labels and summaries. "none" (都不是) and "later" (以后再说) put the card away until selfvoice.due brings it again.body: JSON object

/api/audio ​

MethodDescriptionParameters
GETWAV of [start, end] (+0.2 s each side) cut from the uploaded pieces of device (any when not given) covering it.start (query, string)
end (query, string)
device? (query, string)

/api/listen ​

MethodDescriptionParameters
GETListening in on a device (the live page polls it): its audio after after (unix ms, the X-After of the answer before; none: its newest piece) as WAV, X-Start the first piece's start; 204 when there is nothing newer.device (query, string)
after? (query, integer)

/api/stars ​

MethodDescriptionParameters
GETThe starred sentences as they read now (a rename shows), the newest star first, with their conversation's start and title. ponytail: a star whose row a replay re-cut (another start, another id) drops out of the list.—

/api/sentences/{sid}/star ​

MethodDescriptionParameters
PUT(no docstring)sid (path, string)
body: JSON object
DELETE(no docstring)sid (path, string)

/api/sentences/{sid}/speaker ​

MethodDescriptionParameters
POSTThis sentence was said by to.sid (path, string)
body: JSON object