WordloopWordloop
WorkMeeting RecordingTechnical Design DocContracts

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

AuthbearerAuth
IdempotencyRequired
Echo suppressionClient-Session-Id optional
Response201 Created with Meeting + Location: /meetings/{id}
Side effectsBroadcasts EntityChangedEvent { entity: "meeting", action: "created" }
{
  "title": "Weekly Product Review",
  "source_type": "live",
  "start_time": "2026-05-01T09:00:00Z"
}

GET /meetings

AuthbearerAuth
Response200 MeetingList
Query paramscursor, 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}

AuthbearerAuth
Response200 Meeting
Query paramsexpand (comma-separated: transcription, synthesis, tasks, attendees, recording)
Errors404 meeting not found

PATCH /meetings/{id}

AuthbearerAuth or service auth
Echo suppressionClient-Session-Id optional
Response200 Meeting
Side effectsBroadcasts 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}

AuthbearerAuth
Response204 No Content
Errors409 active recording in progress
Side effectsBroadcasts EntityChangedEvent { entity: "meeting", action: "deleted" }

POST /meetings/{id}/speaker-labelsNew

AuthbearerAuth
IdempotencyRequired
Echo suppressionClient-Session-Id optional
Response200 SpeakerLabelAssignment
Errors404 meeting not found; 422 person not found; 422 speaker label not present in the meeting
Side effectsBroadcasts 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-urlNew

AuthbearerAuth
Response200 AudioPlaybackUrl
Cache-Controlprivate, no-store
Errors404 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
}

On this page