Local API
The desktop app listens on http://127.0.0.1:47321 while it is running. It binds to the loopback interface only, rejects any other Host header, and requires a bearer token on every request except /v1/health and /v1/pair. The MCP gateway and the Chrome extension are both clients of this API; you can be one too.
Authentication
Authorization: Bearer <contents of api_token>
The token is written to api_token in the data folder on first launch, readable by your user only. It is local and personal: never commit it, share it, or send it anywhere. Requests without a valid token receive 401, except /v1/health, which then returns only ok and the version, and /v1/pair, which exchanges a pairing code for the token.
Endpoints
All requests and responses are JSON. Errors return { "error": { "code": 401, "message": "…" } }, where code is the HTTP status.
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/health |
{ ok, version, recording: { active, meeting_id? } } |
| GET | /v1/meetings?q=&limit=&offset=&from=&to= |
List meetings; q runs a full-text search |
| GET | /v1/meetings/:id |
One meeting with notes, action items, markers and its record; from 0.3.13, record.hidden_lines lists the lines hidden as a probable echo, each with echo_of, the line it repeats; from 1.0, record.passport is the Consent Passport once one was built for the meeting (the setting is on when its transcript lands, or it was built from the meeting page): verdict is detected, not_detected, objection or no_other_side, with at_ms, quote, notice_line, reply_line and hint; null before that |
| GET | /v1/meetings/:id/transcript?format=json|md|srt|vtt|txt |
Transcript in the format you choose (a format the plan does not export answers 403, with the reason) |
| POST | /v1/meetings/:id/notes |
{ template_id? } writes the notes again with that template (default general); on Free, after the trial, it counts toward the 3 AI notes a month |
| POST | /v1/meetings/:id/retranscribe |
Rebuilds the transcript from the meeting’s audio |
| PATCH | /v1/meetings/:id |
Update title, tags, starred, attendees |
| DELETE | /v1/meetings/:id |
Delete the meeting: database rows and audio |
| GET | /v1/action_items?meeting_id=&open_only= |
Action items across meetings, each with its meeting’s title |
| GET | /v1/search?q= |
Full-text search across transcript segments and notes, with snippets |
| POST | /v1/recording/prompt |
{ source_app, title?, url?, attendees? } shows the confirm-to-record card; returns { prompt_id } |
| POST | /v1/recording/start |
{ source_app?, title?, attendees? } starts a recording (requires Allow agents to start recordings in Settings → Integrations) |
| POST | /v1/recording/stop |
Stops the current recording |
| POST | /v1/recording/marker |
{ label? } bookmarks the current moment |
| POST | /v1/import |
{ path, title? } transcribes an existing audio file (WAV natively, other formats through ffmpeg, or afconvert on macOS; regular files up to 4 GB with a known audio extension). Requires Allow agents to start recordings |
| GET | /v1/ledger |
The Privacy Ledger: a summary and the latest 200 requests the app has recorded, each with its scope (loopback, lan or internet) |
| POST | /v1/pair |
{ code } exchanges an 8-digit pairing code for a token (see Extension pairing) |
| GET | /v1/events |
Server-Sent Events stream |
Nothing here can make the app record without a person having chosen it: the prompt endpoint only asks, and the start endpoint is refused unless Allow agents to start recordings is on in Settings → Integrations.
Meeting JSON
{
"id": "01J…", "title": "Weekly sync", "source_app": "meet",
"started_at": 1725270000000, "ended_at": 1725273600000, "duration_ms": 3600000,
"status": "ready", "language": "en", "attendees": [{ "name": "Ana" }], "tags": ["sales"],
"starred": false,
"notes": [{ "id": "…", "kind": "summary", "content_md": "…" }],
"action_items": [{ "id": "…", "text": "Send deck", "owner": "You", "done": false }],
"markers": [{ "id": "…", "at_ms": 120000, "label": "pricing" }]
}
status moves through recording, transcribing, summarising and ready, or ends as failed when a recording produced no transcript.
Transcript JSON
{ "meeting_id": "…", "segments": [{ "speaker": "You", "channel": "mic", "start_ms": 0, "end_ms": 2100, "text": "Hi all" }] }
channel is mic (you) or sys (everyone else). The remote channel is one speaker today.
Events
GET /v1/events streams Server-Sent Events. The event name is the type; the data is { type, data, at }.
| Type | Data |
|---|---|
recording.started |
{ meeting_id, source_app, title, started_at } |
recording.stopped |
{ meeting_id, status } |
recording.paused |
{ paused } |
recording.autostopped |
{ meeting_id, source_app, title, grace_s } |
segment.new |
a transcript segment |
meeting.updated, meeting.ready, meeting.deleted |
{ meeting_id } |
prompt.shown |
the pending prompt |
prompt.answered |
{ prompt_id, accepted, answer } |
marker.added |
the marker |
notes.progress |
{ meeting_id, stage, provider?, model?, started_at?, local?, chunk?, chunks? } |
notes.fallback |
{ meeting_id?, reason } |
models.loading, models.loaded |
{ model } |
pair.completed |
{} |
pair.failed |
{ reason } |
permission.asking |
{ kind } |
notice |
{ message } |
error |
{ message } |
The port
The port is fixed at 47321. If another program already uses it, the app cannot start its local API, and Settings → Integrations says so. Quit the other program, which is most often a second copy of Recordist, then open Recordist again.
Importing a caption transcript
POST /v1/import/transcript turns a call's caption transcript into a meeting, with notes. The Chrome extension sends one when you press the Recordist button in its side panel, and your own scripts can send one too.
POST /v1/import/transcript
Authorization: Bearer <token>
Content-Type: application/json
{
"ref": "6f1c0e9e-1b2c-4d5e-8f90-a1b2c3d4e5f6",
"source_app": "meet",
"title": "Weekly sync",
"started_at": 1793095320000,
"ended_at": 1793097660000,
"origin": "captions",
"segments": [
{ "speaker": "Ana Costa", "text": "Morning, everyone.", "at": 1793095320000, "until": 1793095324000 },
{ "speaker": null, "text": "Revenue is up eight percent.", "at": 1793095380000, "until": 1793095382000 }
],
"participants": [{ "name": "Ana Costa" }]
} refis your id for the transcript: 1 to 100 letters, digits,-or_. The samerefsent again is answered with the first meeting.source_appismeet,teamsorzoom.originiscaptions, or left out.titleis optional. Without one the meeting is called after the call app ("Google Meet call"), and the notes may give it a better name.- Times are unix milliseconds.
speakeris the name the captions showed, ornull.You, or the name under Your name on invites in Settings → Detection, is you. Teams captions show your name rather than You, so set it there and your lines stay yours. participantsis optional and takes names only. Without it, the people named in the captions are the meeting's attendees.- Fields Recordist does not know are ignored.
A new meeting answers 201:
{ "meeting_id": "01J…", "kept": "captions", "updated": false, "notes": "writing" } | Status | When |
|---|---|
201 | A new meeting. notes is writing, off when automatic notes are off in Settings, or allowance_used when this month's AI notes on Free are used, and the meeting is there without notes. |
200 | The same ref came before (notes is unchanged), or Recordist recorded the same call itself (kept and notes are recording): the recording stays the record, and nothing of the captions is stored. |
400 | A transcript Recordist cannot take: no lines, too many, times that do not add up, another call app or origin, or no ref it can keep. |
401 | No token, or one Recordist did not issue. |
403 | Transcripts are switched off in Recordist's Settings; an organisation turned agent recording off; or a program other than the paired extension sent it while Allow agents to start recordings is off. |
409 | The same ref again, with more lines than the meeting holds. Delete that meeting in Recordist, then send it again to keep the whole call. |
413 | The request is larger than 4 MB. |
Every refusal carries one plain sentence in error.message that you can show as it is. No answer carries a title, a name or a line of the transcript.
A transcript holds up to 5,000 lines, in a request of up to 4 MB, from a call of up to 24 hours within the last year. A line longer than 4,000 characters, such as one person's long turn, is kept as several lines.
The token the Chrome extension got at pairing may send a transcript, and it still cannot read meetings. A token from New token for another program, or the MCP gateway's, may send one once Allow agents to start recordings is on in Settings → Integrations. The person switches transcripts on or off in Recordist's Settings, and on macOS an organisation's DisableAgentRecording turns them off for every program.
The meeting has the tag captions and no audio, and its source_app is import, because Recordist did not record the call. Your lines are on the mic side and everyone else's on the sys side, under the names the captions showed. Notes are written as for any meeting; on Free they count toward the month's AI notes. With a token, GET /v1/health also answers transcript_import: true while a transcript would be taken.