Meeting
Top-level resource — CRUD, expand parameter, speaker-label assignment, and audio playback URL.
Meeting
A meeting is the top-level entity. It represents a conversation — whether captured live, uploaded as a file, or created as ad-hoc notes. A meeting owns its transcription, synthesis, tasks, and audio. Recording is available via ?expand=recording for meetings that have one. For shared concerns that apply across all entities — authentication, error format, idempotency, echo suppression — see Infrastructure.
For the full recording lifecycle — commands, events, binary audio, ML session, and gap repair — see Recording.
Resource Shape
{
"id": "meeting-uuid",
"title": "Weekly Product Review",
"headline": "Rollout plan review",
"source_type": "live",
"start_time": "2026-05-01T09:00:00Z",
"end_time": "2026-05-01T10:00:00Z",
"created_at": "2026-05-01T08:59:00Z",
"attendees": [],
"notes": "## Action Items\n- Follow up with design team\n- Review rollout plan",
"transcription": {
"id": "transcription-uuid",
"status": "completed"
},
"synthesis": {
"summary": "The team aligned on rollout sequencing.",
"topics": [],
"talking_points": []
}
}headline is auto-generated by ML from the meeting content and present on all meetings regardless of source type. It is included in the compact list shape so it can be displayed when listing meetings. ML writes it via PATCH /meetings/{id} with service auth.
Expand parameter: GET /meetings/{id} supports ?expand=transcription,synthesis,tasks,attendees,recording to control which nested resources are included. Without expansion, only the top-level fields and summary references (e.g., transcription.id, transcription.status) are returned. recording is expand-only — it is never present in the default or compact shapes. List endpoints (GET /meetings) always return the compact form.
Expanded: recording — when ?expand=recording is included and the meeting has a recording:
{
"recording": {
"status": "completed",
"started_at": "2026-05-01T09:00:00Z",
"stopped_at": "2026-05-01T10:00:00Z",
"stop_reason": "user_requested",
"last_received_sequence": 36000,
"audio_available": true
}
}If the meeting has no recording, the recording field is null even when expanded.
Valid source_type values: live, upload, text, anecdotal.
REST API
POST /meetings
| Auth | bearerAuth |
| Idempotency | Required |
| Echo suppression | Client-Session-Id optional |
| Response | 201 Created with Meeting + Location: /meetings/{id} |
| Side effects | Broadcasts EntityChangedEvent { entity: "meeting", action: "created" } |
{
"title": "Weekly Product Review",
"source_type": "live",
"start_time": "2026-05-01T09:00:00Z"
}GET /meetings
| Auth | bearerAuth |
| Response | 200 MeetingList |
| Query params | cursor, limit, has_active_recording New, source_type |
The compact list shape includes: id, title, headline, source_type, start_time, end_time, created_at, attendees (compact: id + display_name only), and transcription (compact: id + status only). Recording, synthesis, and tasks are never included in the list shape — use the detail endpoint with ?expand to fetch them. New: has_active_recording=true filters to meetings with an active live recording — the app uses this as the read-only guard for disabling Start Live Recording.
GET /meetings/{id}
| Auth | bearerAuth |
| Response | 200 Meeting |
| Query params | expand (comma-separated: transcription, synthesis, tasks, attendees, recording) |
| Errors | 404 meeting not found |
PATCH /meetings/{id}
| Auth | bearerAuth or service auth |
| Echo suppression | Client-Session-Id optional |
| Response | 200 Meeting |
| Side effects | Broadcasts EntityChangedEvent { entity: "meeting", action: "updated" } |
User update:
{
"notes": "## Action Items\n- Follow up with design team"
}ML write-back (service auth):
{
"headline": "Rollout plan review"
}DELETE /meetings/{id}
| Auth | bearerAuth |
| Response | 204 No Content |
| Errors | 409 active recording in progress |
| Side effects | Broadcasts EntityChangedEvent { entity: "meeting", action: "deleted" } |
POST /meetings/{id}/speaker-labels — New
| Auth | bearerAuth |
| Idempotency | Required |
| Echo suppression | Client-Session-Id optional |
| Response | 200 SpeakerLabelAssignment |
| Errors | 404 meeting not found; 422 person not found; 422 speaker label not present in the meeting |
| Side effects | Broadcasts EntityChangedEvent { entity: "transcript_segment", action: "updated" }. If a live session is active, sends SpeakerStateUpdatedEvent to ML over the ML WebSocket. |
Request:
{
"speaker_label": "speaker_1",
"person_id": "person-uuid"
}Response:
{
"meeting_id": "meeting-uuid",
"speaker_label": "speaker_1",
"person_id": "person-uuid",
"state": "manual",
"updated_segment_count": 27
}For how speaker labels feed the identification pipeline, see Person & Speaker Identity.
GET /meetings/{id}/audio-url — New
| Auth | bearerAuth |
| Response | 200 AudioPlaybackUrl |
| Cache-Control | private, no-store |
| Errors | 404 meeting not found; 404 audio still composing or unavailable |
{
"url": "https://storage.googleapis.com/signed-url",
"expires_at": "2026-05-01T10:00:00Z",
"mime_type": "audio/webm",
"duration_ms": 3610000
}