Skip to content

The session API

Updated View as Markdown

src/api.rs holds one dispatcher, api::call(method, params, paths). ambient mcp (../using/mcp.md) is a JSON-RPC parser in front of it today, and the window (../adr/0017-one-web-page-for-the-window.md) calls the same function directly — one dispatcher that both cannot drift apart on, because there is only one implementation of each verb.

A method either answers with a JSON value or fails with an ApiError: InvalidParams is the caller’s bug (ambient mcp maps it to JSON-RPC -32602), Failed is a call that ran and could not answer (ambient mcp maps it to a tool result with isError: true).

sessions

Every recorded session on this machine, pinned sessions first and newest first within each group. Saved notes are included so reopening the editor preserves them. Capture warnings remain visible in the window, and audio_available tells the page whether retained audio can be used to separate voices.

Request:

{}

Reply:

[
  {
    "id": "2026-09-05T1200",
    "name": null,
    "started_at": "2026-09-05T12:00:00+01:00",
    "duration_s": 612.5,
    "transcribed": true,
    "live": false,
    "transcribing": false,
    "error": null,
    "tags": [],
    "notes": "",
    "warnings": [],
    "audio_available": true,
    "pinned": false
  }
]

transcript

The lines of one session, including one being recorded now.

Request:

{"session": "2026-09-05T1200", "since": 4, "verbatim": false}

since and verbatim are optional; since defaults to 0 and verbatim to false.

Reply:

{
  "session": "2026-09-05T1200",
  "state": "done",
  "next": 6,
  "lines": [{"track": "room", "start_ms": 5000, "end_ms": 6000, "text": "…"}]
}

Case-insensitive substring search over every session’s folded transcript, newest session first.

Request:

{"query": "budget", "limit": 50}

limit is optional and defaults to 50.

Reply:

[
  {
    "session": "2026-09-05T1200",
    "index": 3,
    "track": "room",
    "start_ms": 5000,
    "speaker": null,
    "text": "the budget review is at noon"
  }
]

index is the line’s position in transcript_appended order — the order lines were written, not the order their clocks say — so it can be used to re-fetch the same line from transcript.

session.update

Rename a session, add or remove one tag, replace its notes, or pin it — whichever fields are present in the request. Rewrites session.json only; raw.jsonl and edits.jsonl are untouched.

Request:

{"session": "2026-09-05T1200", "name": "Standup", "add_tag": "1:1", "pinned": true}

name, notes, pinned, add_tag and remove_tag are all optional; only the fields present are applied. add_tag of a tag already on the session and remove_tag of one that is not are no-ops, so two callers each adding a different tag both keep theirs.

Reply, the session’s full metadata after the change:

{
  "id": "2026-09-05T1200",
  "name": "Standup",
  "started_at": "2026-09-05T12:00:00+01:00",
  "ended_at": "2026-09-05T12:10:12+01:00",
  "duration_s": 612.5,
  "device_hz": 48000,
  "mic_hz": 48000,
  "channels": 1,
  "mic_channels": 1,
  "apps": [],
  "model": "parakeet",
  "warnings": [],
  "tags": ["1:1"],
  "notes": "",
  "pinned": true
}

A session still recording has no session.json yet and answers Failed saying so; a session a transcriber currently holds the lock on answers Failed saying it is being transcribed.

session.delete

Remove a session directory and its audio entirely. The window shows its own confirm dialog before calling this; the API does not ask again.

Request:

{"session": "2026-09-05T1200"}

Reply:

{"session": "2026-09-05T1200", "deleted": true}

Refuses a live capture — the session recording now, or either native scratch track still growing — saying the session is still recording, and a session a transcriber currently holds the lock on answers Failed saying it is being transcribed. A directory with no session.json and no fresh audio is a failed capture and is removed like any other.

export

A session rendered as markdown, text, json, srt, vtt or assistant. Dispatcher-only: not in methods() and not offered over MCP, the same posture as search.

Request:

{"session": "2026-09-05T1200", "format": "srt"}

format is optional and defaults to "markdown"; a name it does not recognise is InvalidParams naming all six.

Reply:

{
  "session": "2026-09-05T1200",
  "format": "srt",
  "text": "1\n00:00:05,000 --> 00:00:06,000\nthe budget review is at noon\n"
}

Every format but markdown reads the raw transcript folded with edits.jsonl, not markdown’s bleed-deduped view, so exporting a call with speakerphone overlap keeps both copies where the markdown document would drop one.

config.get

The settings payload behind the window’s Settings page: the same shape src/settings.rs’s push sends it today, minus unnamed (now speakers.unnamed, which takes a session).

Request:

{}

Reply:

{
  "apps": [],
  "input_device": null,
  "diarize": true,
  "threshold": 0.55,
  "sessions_dir": null,
  "devices": ["MacBook Pro Microphone"],
  "default_dir": "/Users/you/Documents/Ambient",
  "ask_before_recording": true,
  "audio_retention": "7",
  "roster": ["Ana"],
  "latest_session": "2026-09-05T1200"
}

devices is whatever CoreAudio reports as attached microphones right now, so its contents vary between calls. audio_retention is a string ("forever" or a number of days as a string); the settable key is audio_retention_days, a number or "forever".

config.set

Change one setting, then answer the fresh config.get payload.

Request:

{"key": "diarize", "value": "off"}

Reply: config.get’s reply, with the change applied.

Refuses sessions_dir while a session is recording, saying so; refuses a key Config::set does not recognise as InvalidParams. Nothing is written on either refusal.

roster.list

Every name on the roster, alphabetically.

Request:

{}

Reply:

["Ana"]

roster.add

Add a name to the roster (a no-op if it is already there), then answer the roster.

Request:

{"name": "Ana"}

Reply:

["Ana"]

roster.remove

Remove a name from the roster, then answer the roster. Does not unname anyone in a past recording — those names live in each session’s edits.jsonl.

Request:

{"name": "Ana"}

Reply:

[]

speakers.name

Give every line labelled label in one session the name name. Appends to edits.jsonl; raw.jsonl is untouched.

Request:

{"session": "2026-09-05T1200", "label": "SPEAKER_00", "name": "Ana"}

Reply:

{"renamed": 2}

speakers.undo

Take back the newest batch of names a person typed for one session.

Request:

{"session": "2026-09-05T1200"}

Reply:

{"reverted": 2}

speakers.unnamed

Speaker labels nobody has named yet in one session, each with the first thing that voice said.

Request:

{"session": "2026-09-05T1200"}

Reply:

[
  {"label": "call-1", "sample": "shall we start with the export spec"},
  {"label": "room-1", "sample": "yes, go ahead"}
]

devices

Input device names, for the Settings page’s device picker.

Request:

{}

Reply:

{"devices": ["MacBook Pro Microphone"]}

status

What Ambient is doing right now: the live session, if any, and what is waiting.

Request:

{}

Reply:

{"live": null, "awaiting_transcript": 0, "sessions": 3}

doctor

The same ten checks ambient doctor prints, in doctor::run’s fixed order, for the window’s Welcome page. Each check is ok or not; when it is not, the fix is the detail string.

Request:

{}

Reply:

[
  {"name": "models/root", "ok": true, "detail": "/Users/you/Library/Application Support/ambient/models"},
  {"name": "models/asr", "ok": true, "detail": "…"},
  {"name": "config", "ok": true, "detail": "defaults"},
  {"name": "sessions/writable", "ok": true, "detail": "/Users/you/Documents/Ambient"},
  {"name": "sessions/stale-lock", "ok": true, "detail": "none"}
]
Navigation

Type to search…

↑↓ navigate↵ selectEsc close