# Telnyx Agent Tools: Meeting (Beta) — Full Documentation > Complete page content for Meeting (Beta) (Agent Tools section) of the Telnyx developer docs (https://developers.telnyx.com). > This file: https://developers.telnyx.com/docs/development/llms/agent-tools-meeting-beta-llms-full-txt · Root index: https://developers.telnyx.com/llms.txt ## Meeting (Beta) ### Overview > Source: https://developers.telnyx.com/docs/meeting.md Telnyx Meeting gives your agent a seat in third-party meetings. Point it at a Google Meet, Zoom, Teams, or Webex link and a programmable participant joins the room -- it captures the live transcript, can speak and post to chat, and generates summaries and action items. The virtual participant, aka bot, runs on Telnyx primitives: Telnyx STT for transcription, Telnyx TTS for speaking, and Telnyx Inference for summaries and action items. ## The Workflow Point the bot at a meeting URL. It joins as a visible participant and starts capturing audio. Join now or schedule ahead with `join_at`. While the meeting runs, read the transcript as people speak, plus who joined and what was said in chat. The bot is a participant, not just a recorder -- it can speak back, post to chat, and be interrupted. When the meeting ends, the transcript is finalized and a summary with action items is generated via Telnyx Inference. Read them directly, download recordings, or have them pushed to a webhook. ## Supported Platforms Google Meet, Zoom, Microsoft Teams, and Webex. The platform is detected automatically from the meeting URL. ## Next Steps - [Quick Start](/docs/meeting/quick-start) -- send the bot to a meeting and read the results, four curl commands end to end - [Meeting MCP Server](/docs/meeting/mcp) -- connect an AI agent to 19 dedicated Meeting tools over stateless Streamable HTTP - [Join a Meeting](/docs/meeting/join-meeting) -- create a meeting session and send the bot - [Live Transcript](/docs/meeting/live-transcript) -- follow the transcript stream in real time - [Meeting Presence](/docs/meeting/presence) -- how the bot appears in the meeting, from its roster name to its camera tile - [Controlling the Bot](/docs/meeting/control) -- make it speak, post to chat, or run an assistant - [Collect Results](/docs/meeting/collect-results) -- read the finalized transcript and summary - [Webhooks](/docs/meeting/webhooks) -- receive session events on your own endpoint --- ### Quick Start > Source: https://developers.telnyx.com/docs/meeting/quick-start.md Send the bot to a real meeting and get back a transcript, summary, and action items. All you need is a Telnyx API key and a meeting link. Create a meeting session pointing at your meeting URL: ```bash curl -X POST https://api.telnyx.com/v2/meeting_sessions \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "meeting_url": "https://meet.google.com/abc-defg-hij", "bot_name": "Notetaker", "summarize_on_end": true }' ``` Returns `201 Created` with the session `id`: ```json { "data": { "id": "mtgsess_9b2f...", "status": "joining", "platform": "google_meet", "recording": false } } ``` `summarize_on_end` attempts a `summary` artifact after the transcript is finalized, so a recap is ready without a second request. It does not generate action items -- request an `action_items` artifact separately -- and an empty transcript or a generation failure can result in no completed summary. The bot appears in the meeting lobby under its `bot_name`; admit it like any other participant. Poll the session until `status` is `active`: ```bash curl -H "Authorization: Bearer $TELNYX_API_KEY" \ "https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f..." ``` A non-null `joined_at` is positive evidence that the bot became active. `admission_denied` is reserved for an explicit denial by the host; a never-admitted session can also end as plain `ended`, so do not infer attendance from `ended` alone. The full status lifecycle is on [Join with a Meeting URL](/docs/meeting/join-meeting/meeting-url). Long-poll the transcript endpoint while the meeting runs. `after` is your cursor; `wait_seconds` holds the request open until a new segment lands: ```bash curl -H "Authorization: Bearer $TELNYX_API_KEY" \ "https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../transcript?after=0&wait_seconds=20" ``` ```json { "data": [ { "seq": 41, "text": "let's ship it", "speaker_label": "Ada L.", "confidence": 0.94, "relative_ts": 1240.5, "occurred_at": "2026-06-16T09:00:42Z" } ], "meta": { "next_after": 41 } } ``` Repeat the call with `after` set to `meta.next_after` (the last `seq` you received). When an empty page returns `"next_after": null`, keep the cursor you already had -- null is not a replacement cursor. When the meeting ends -- or you `DELETE` the session to make the bot leave -- the transcript is finalized and the summary generates. Read the artifacts: ```bash curl -H "Authorization: Bearer $TELNYX_API_KEY" \ "https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../artifacts" ``` Poll until the artifact's `status` is `completed`, then read `content.text` for the recap. To get action items, `POST` to the same `artifacts` endpoint with `{"type": "action_items"}`. The finalized transcript stays available at the `transcript` endpoint. ## Related - [Join a Meeting](/docs/meeting/join-meeting) -- scheduling, calendar auto-join, and the session lifecycle - [Meeting MCP Server](/docs/meeting/mcp) -- run the Meeting workflow from an MCP-compatible AI agent - [Live Transcript](/docs/meeting/live-transcript) -- long polling and the WebSocket stream in depth - [Controlling the Bot](/docs/meeting/control) -- make the bot speak, post to chat, and be interrupted - [Collect Results](/docs/meeting/collect-results) -- artifacts, recordings, and deletion - [Webhooks](/docs/meeting/webhooks) -- push session events to your endpoint instead of polling --- ## Join a Meeting ### Join a Meeting > Source: https://developers.telnyx.com/docs/meeting/join-meeting.md The bot joins your meeting as a visible participant and starts capturing audio. There are two ways to get it into a meeting -- use whichever fits your workflow. ## Choose a Method | | Meeting URL | Calendar auto-join | | --- | --- | --- | | Setup | Create a session pointing at a meeting URL | Connect Google Calendar once | | Effort | Build it yourself with `POST /v2/meeting_sessions` | The bot joins automatically, nothing to build | | Control | Full control over each join, including scheduling | Per-connection policy, session defaults, and per-meeting overrides | | Best for | Custom apps and precise scheduling | Automatically capturing your own meetings | Create a session with the REST API. Full control over each join, including scheduled joins with `join_at`. Connect Google Calendar once and the bot joins your meetings with a link automatically. ## The Bot's Camera Tile A session can show a static JPEG as the bot's camera tile, set with `camera_image` on the create request. The full contract -- sources, size limits, and how a digital avatar overrides it -- is on [Still images](/docs/meeting/presence/still-images). ## Announce the Bot on Arrival The bot can introduce itself as it is admitted, by voice with `speak_on_enter` or in chat with `chat_on_enter`. See [Announce the Bot on Arrival](/docs/meeting/join-meeting/introductions). --- ### Meeting URL > Source: https://developers.telnyx.com/docs/meeting/join-meeting/meeting-url.md Create a meeting session by pointing the bot at a meeting URL. The bot joins as a visible participant and starts capturing audio. You can join now, schedule the join for a future time, or use [calendar auto-join](/docs/meeting/join-meeting/calendar-auto-join) to have the bot join your meetings automatically. ## Create a Meeting Session ```bash curl -X POST https://api.telnyx.com/v2/meeting_sessions \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "meeting_url": "https://meet.google.com/abc-defg-hij", "bot_name": "Notetaker", "summarize_on_end": true }' ``` Returns `201 Created`: ```json { "data": { "id": "mtgsess_9b2f...", "status": "joining", "platform": "google_meet", "recording": false } } ``` If you send the same `idempotency_key` again, the request is replayed and you get `200 OK` with the original session instead of a duplicate. The full request schema -- every field, constraint, and default -- lives on [Create a meeting session](/api-reference/meeting-sessions/create-a-meeting-session) in the API Reference. ### Platform Detection The platform is detected automatically from the meeting URL: | URL pattern | Platform | | --- | --- | | `meet.google.com/...` | `google_meet` | | `*.zoom.us/...` | `zoom` | | `teams.microsoft.com/...` | `teams` | | `teams.live.com/...` | `teams` | | `*.webex.com/...` | `webex` | During the beta, creating a session with a `teams.microsoft.com` URL can fail with a gateway `502` while Teams support rolls out; `teams.live.com` URLs, Google Meet, Zoom, and Webex URLs create sessions normally. ## Session Statuses A session moves from `joining` to `waiting_for_admission` when the bot reaches the lobby, and to `active` once it enters the room -- `active`, and a non-null `joined_at`, are the positive evidence that the bot got in. It finishes as `ended`, `failed`, or `admission_denied`. `admission_denied` is reserved for an explicit denial by the host; a session that was cancelled or timed out without being admitted ends as `ended` with `joined_at: null`, so ending alone does not prove attendance. Scheduled sessions wait in `scheduled` until `join_at`. Every status and its meaning is documented on [Retrieve a meeting session](/api-reference/meeting-sessions/retrieve-a-meeting-session). ## Schedule a Join Set `join_at` to a future timestamp. The bot joins when the meeting starts: ```bash curl -X POST https://api.telnyx.com/v2/meeting_sessions \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "meeting_url": "https://meet.google.com/abc-defg-hij", "join_at": "2026-08-05T16:00:00Z" }' ``` While a session is `scheduled`, update it with `PATCH`: ```bash curl -X PATCH https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f... \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "join_at": "2026-08-05T17:00:00Z" }' ``` Only `join_at` and `bot_name` can be updated, and only while the session is `scheduled`. ## Leave or Cancel a Session `DELETE` ends the bot's participation. It does not delete the session history, transcript, or artifacts: ```bash curl -X DELETE https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f... \ -H "Authorization: Bearer $TELNYX_API_KEY" ``` ## Errors Failures return a non-2xx status with an error envelope: ```json { "error": { "code": "not_found", "message": "meeting session not found" } } ``` ## Related - [Calendar Auto-join](/docs/meeting/join-meeting/calendar-auto-join) -- the bot joins your meetings automatically - [Live Transcript](/docs/meeting/live-transcript) -- follow the transcript stream in real time - [Meeting Presence](/docs/meeting/presence) -- how the bot appears in the meeting - [Collect Results](/docs/meeting/collect-results) -- read the finalized transcript and summary --- ### Calendar Auto-join > Source: https://developers.telnyx.com/docs/meeting/join-meeting/calendar-auto-join.md Connect Google Calendar once and the bot attends your meetings automatically: Meeting schedules a bot for eligible calendar events that have a supported join link. Each auto-joined meeting becomes a normal session with the same [live transcript](/docs/meeting/live-transcript), [recordings and results](/docs/meeting/collect-results), and [webhooks](/docs/meeting/webhooks). Outlook is reserved for a future release. ## Calendar API The Calendar API includes eight routes: | Method | Route | Purpose | | --- | --- | --- | | `POST` | `/v2/calendar_connections` | Start Google OAuth and return `pending_oauth` plus `oauth_url`; no connection row exists until the callback succeeds. | | `GET` | `/v2/calendar_connections` | List connections owned by the authenticated connecting user. | | `GET` | `/v2/calendar_connections/{id}` | Retrieve one connection owned by that user. | | `PATCH` | `/v2/calendar_connections/{id}` | Update policy, bot name, or future-session defaults. | | `DELETE` | `/v2/calendar_connections/{id}` | Disconnect and stop future joins for that connection. | | `GET` | `/v2/calendar_connections/{id}/meetings` | List tracked meetings; defaults to the next seven days and accepts an ISO-8601 `from`/`to` window up to 31 days. | | `PATCH` | `/v2/calendar_connections/{id}/meetings/{meeting_id}` | Set `bot_will_join` to `true`, `false`, or `null` (follow policy). | | `GET` | `/v2/meeting_bot/calendar/oauth_callback` | Browser OAuth callback. Identity comes from verified, short-lived signed state; clients do not call it with bearer auth. | ## Connect Your Calendar ```bash curl -X POST https://api.telnyx.com/v2/calendar_connections \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "platform": "google_calendar" }' ``` ```json { "data": { "platform": "google_calendar", "status": "pending_oauth", "oauth_url": "https://accounts.google.com/o/oauth2/v2/auth?..." } } ``` Open `oauth_url` in a browser and grant read-only calendar access. After the callback succeeds, list the connection to obtain its `id` and connected status. ## Configure Auto-join ```bash curl -X PATCH https://api.telnyx.com/v2/calendar_connections/calconn_... \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "auto_join_policy": "external_organizer_excluded", "bot_name": "Calendar Notetaker", "default_barge_in": false, "default_summarize_on_end": true, "default_voice": null }' ``` | Policy | Behavior | | --- | --- | | `all_meetings` | Join eligible events that have a supported meeting link. | | `external_organizer_excluded` | Skip Google events where `organizer.self` is not `true`. This means the connected calendar user did not organize the event; it is not a domain comparison, so a coworker's same-domain event is also excluded. | | `per_meeting` | Join only meetings explicitly opted in with the per-meeting PATCH route. | Connection defaults apply to sessions adopted after the change; they do not rewrite already-created sessions. `bot_will_join` in a meeting response is the effective decision under policy, while `bot_will_join_override` is the explicit nullable override. ## Timing, Ownership, and Identity Retention Calendar scheduling applies the configured join lead time. If `event.start_time - lead_time` is still in the future, the bot starts joining then; if that lead window has elapsed, it starts at the event start time rather than using a past `join_at`. Calendar connections, settings, and tracked-meeting controls are owned by the user who connected the calendar. Sessions generated from those meetings, and their transcripts, recordings, events, artifacts, and usage data, are organization-level Meeting resources visible to other authenticated users or API keys in the same Telnyx organization. Organizer and attendee identities are retained only for a configured post-event window (currently 24 hours by default) and are then scrubbed from Calendar meeting records. Disconnecting also erases those Calendar identity fields. This identity-retention boundary does not delete the separately retained organization-level Meeting session resources. ## Related - [Join with a meeting URL](/docs/meeting/join-meeting/meeting-url) -- create sessions through the API - [Live Transcript](/docs/meeting/live-transcript) -- follow the transcript stream in real time - [Collect Results](/docs/meeting/collect-results) -- read the finalized transcript and summary --- ### Announce the Bot on Arrival > Source: https://developers.telnyx.com/docs/meeting/join-meeting/introductions.md A bot that appears in a meeting without explaining itself is the thing attendees complain about. Two create parameters let it introduce itself as it is admitted: `speak_on_enter` says a line out loud, `chat_on_enter` posts one to the meeting chat. Both are set when you create the session -- neither is an action, and neither can be changed once the bot is on its way. They are independent and can be used together. ## Post to Chat on Arrival Set `chat_on_enter` -- typically a recording disclosure. It takes 1-4000 characters: ```bash curl -X POST https://api.telnyx.com/v2/meeting_sessions \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "meeting_url": "https://meet.google.com/abc-defg-hij", "chat_on_enter": "Hello, I am Oliver'"'"'s meeting bot assistant. I am recording this session." }' ``` - **Delivered at most once.** Duplicate provider callbacks and service restarts can never repost it. - **Independent of `speak_on_enter`.** Both can be set on one session, and the chat message posts first: it does not wait for text-to-speech or avatar startup. - **Works with an assistant attached.** An assistant owns the voice, not the chat. - Echoed back as `config.chat_on_enter`, `null` when unset. - Appends a `bot.chat_on_enter_delivered` event to the session's [event history](/docs/meeting/events) and the WebSocket stream. There is no new webhook event type. On a platform with no meeting chat -- Webex, or a URL the service does not recognise -- **create is rejected up front with `422 unsupported_capability`** rather than accepting a message that could never be posted. You find out when you ask for it, not by discovering later that nothing was said. ## Speak on Arrival Set `speak_on_enter` to the text the bot should say as soon as it is admitted, with no [speak](/docs/meeting/control/speak) call of your own. It sits alongside `voice`, `barge_in` and `summarize_on_end`, and applies to every session. ```bash curl -X POST https://api.telnyx.com/v2/meeting_sessions \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "meeting_url": "https://meet.google.com/abc-defg-hij", "speak_on_enter": "Hi, I am taking notes for this call." }' ``` With an attached [Telnyx AI Assistant](/docs/meeting/ai-assistants), `speak_on_enter` is delivered through the assistant's output page rather than the bot mic, and only once the assistant reaches `connected` -- so the greeting waits for assistant startup instead of landing the instant the bot is admitted. It is still delivered at most once. `chat_on_enter`, by contrast, posts immediately and does not wait on the assistant. ## Where These Live on the Session `speak_on_enter` is sent **flat on the create request** and read back **nested under `config`** on the session object, along with `voice`, `barge_in` and `summarize_on_end`. Sending them wrapped in `config` is rejected with `400 invalid_request: body: Unrecognized key(s) in object: 'config'`. `chat_on_enter` follows the same rule. ## Related - [Join a Meeting](/docs/meeting/join-meeting) -- the rest of the create request - [Chat](/docs/meeting/control/chat) -- post to chat during the meeting - [Speak](/docs/meeting/control/speak) -- speak during the meeting - [Telnyx AI Assistants](/docs/meeting/ai-assistants) -- what an attached assistant takes over - [Session Events](/docs/meeting/events) -- `bot.chat_on_enter_delivered` --- ## During the Meeting ### Live Transcript > Source: https://developers.telnyx.com/docs/meeting/live-transcript.md While the meeting runs, the bot captures audio and Telnyx STT transcribes it in real time. You have two ways to read segments as they are produced: a WebSocket stream (alpha) or incremental long polling. ## Read the Transcript Incrementally `GET /v2/meeting_sessions/{id}/transcript` returns segments in ascending `seq` order, at most `limit` per page (default 100, maximum 1,000). Use `after` to page forward and `wait_seconds` to hold the request open (up to 25 seconds) so new segments arrive as soon as they are produced: ```bash curl -H "Authorization: Bearer $TELNYX_API_KEY" \ "https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../transcript?after=40&wait_seconds=20" ``` ```json { "data": [ { "seq": 41, "text": "let's ship it", "speaker_label": "Ada L.", "confidence": 0.94, "relative_ts": 1240.5, "occurred_at": "2026-06-16T09:00:42Z" } ], "meta": { "next_after": 41 } } ``` `wait_seconds` turns the call into a long poll: if no new segments exist yet, the request waits and returns as soon as a new segment lands or the timeout elapses (an empty `data` array with `"next_after": null`). Pass `meta.next_after` as the next request's `after` cursor; on an empty page keep the cursor you already had rather than replacing it with null. ## Connect to the WebSocket Stream The stream endpoint replays stored events and transcript segments, then follows the meeting live. Open a connection with your API key on the upgrade: ``` WSS /v2/meeting_sessions/{id}/stream?after_seq=0&after_transcript_seq=0 ``` Send your API key in the `Authorization` header using the Bearer scheme on the upgrade request. Header auth is the only supported mechanism -- query-string credentials such as an `access_token` parameter are rejected with `401`. Browser-native WebSocket clients cannot set the `Authorization` header during the handshake, so connect from a backend and relay to the browser if needed. A successful upgrade returns `101`. Before upgrade, the server can return `401` for missing or invalid credentials or a nonexistent, foreign, or already-terminal session; `403` for a forbidden credential; `429` with `Retry-After` when authentication is overloaded; or `503` while authentication is unavailable. This is a WebSocket upgrade endpoint, not an ordinary HTTP `GET` route. A plain `curl` request -- or a request with only partial upgrade headers -- can reach the Meeting service and return an HTML `Cannot GET` response even when the WebSocket route is available. Use a WebSocket client that performs the complete handshake; verify a successful connection by the `101 Switching Protocols` response and incoming frames. For example, connect from a backend terminal with [`wscat`](https://github.com/websockets/wscat). The Meeting Session must belong to the API-key account and must not be in a terminal state: ```bash export SESSION_ID='mtgsess_...' npx --yes wscat \ --connect "wss://api.telnyx.com/v2/meeting_sessions/${SESSION_ID}/stream?after_seq=0&after_transcript_seq=0" \ --header "Authorization: Bearer [REDACTED]" ``` A successful connection prints `Connected (press CTRL+C to quit)` and begins receiving JSON frames. To test bidirectional commands while the session status is `active`, enter a command such as: ```json { "action": "send_chat", "text": "WebSocket connection verified." } ``` The meeting receives the chat message and the socket receives the corresponding `chat.sent` event. Press Ctrl+C to disconnect. Set `after_seq` and `after_transcript_seq` to the last sequence numbers you have seen. The server replays stored records after those cursors, then switches to live delivery. Replay is bounded: each connection replays at most 1,000 event rows and 1,000 transcript rows, and a `{"kind": "truncated", "store": "events" | "transcript"}` frame signals that more rows remain -- reconnect with that store's cursor set to the last sequence received, or backfill through the REST endpoints. Catch-up and live delivery can overlap, so tolerate duplicates; catch-up event frames carry a durable top-level `seq` while live event frames may not, and a sequence-less live frame must not advance your durable cursor. Transcript segments arrive in two shapes, and a client has to handle both. Segments **replayed from the transcript store** during catch-up arrive as standalone `transcript` frames: ```json { "kind": "transcript", "seq": 17, "text": "Hello, can everyone hear me?", "confidence": 0.98, "speaker_label": "Alice", "relative_ts": 12.4, "occurred_at": "2026-06-16T09:00:12Z" } ``` Segments produced **live** during the meeting arrive instead as event frames, with the segment nested under `payload`: ```json { "kind": "event", "type": "transcript.segment", "payload": { "seq": 18, "text": "Yes, loud and clear.", "confidence": 0.97, "speaker_label": "Bob", "relative_ts": 14.9 }, "occurred_at": "2026-06-16T09:00:14Z" } ``` A client that branches only on `kind == "transcript"` receives nothing while a meeting is live -- silently, with no error -- because every segment produced after connection is an event frame. Other frame kinds (`event`, `error`, `truncated`) and the complete frame protocol -- payload schemas, commands, and close codes -- are specified in the [Meeting Session agent stream reference](/api-reference/websockets/meeting-session-agent-stream). Segment fields (`seq`, `text`, `speaker_label`, `confidence`, `relative_ts`, `occurred_at`) are documented on [List meeting session transcript](/api-reference/meeting-session-data/list-meeting-session-transcript). ### Sending Commands over the Stream You can make the bot act from the same socket rather than over REST. The frames, the 8-command cap, and the missing per-command acknowledgement are documented with the actions themselves, in [Controlling the Bot](/docs/meeting/control#same-commands-over-websocket). The socket's own limits apply either way: inbound frames larger than 32 KiB close the connection with code `1009`, and if the server's outbound buffer backs up it closes with code `1013` and reason `backpressure` -- reconnect with your last durable cursors. ### Availability The WebSocket stream is currently Alpha and is not covered by a stability or availability commitment. The long-polling `transcript` endpoint above works on the standard API and is the supported way to read the transcript today. ## Related - [Session Events](/docs/meeting/events) -- the stored event history behind the stream - [Join a Meeting](/docs/meeting/join-meeting) -- create a meeting session and send the bot - [Controlling the Bot](/docs/meeting/control) -- speak, chat, or hand the meeting to an assistant - [Meeting Presence](/docs/meeting/presence) -- the bot's roster name and on-camera identity - [Collect Results](/docs/meeting/collect-results) -- read the finalized transcript and summary --- ### Recording > Source: https://developers.telnyx.com/docs/meeting/recording.md A meeting session records automatically. Point the bot at a meeting and the recording is produced alongside the transcript -- there is nothing to switch on. That is why `POST /v2/meeting_sessions` has no recording parameter and no action controls it: the session's three actions are [`speak`, `stop_speaking`](/docs/meeting/control/speak) and [`send_chat`](/docs/meeting/control/chat). Recording is not something you start, stop, or configure, so the only calls you make are the ones that read the result afterwards. What that leaves you: - The `recording.available` webhook fires when a recording is ready. It carries only `recording_types` and never URLs. - `GET /v2/meeting_sessions/{id}/recordings` returns the available recordings with short-lived signed download URLs. Re-fetch it whenever you need a current URL rather than storing one. - `DELETE /v2/meeting_sessions/{id}/recording_media` removes the media. The session, its transcript, events and artifacts are kept. - A [digital avatar's](/docs/meeting/digital-avatars) camera video is not included in the provider recording; the bot's audio is. Fetching and deleting recordings is covered with the rest of the post-meeting data in [Collect Results](/docs/meeting/collect-results#recordings). ## Related - [Collect Results](/docs/meeting/collect-results) -- fetch recordings, the finalized transcript, and artifacts - [Webhooks](/docs/meeting/webhooks) -- receive `recording.available` - [Session Events](/docs/meeting/events) -- the ordered event history for a session --- ### Meeting Presence > Source: https://developers.telnyx.com/docs/meeting/presence.md The bot is a participant, not just a recorder. Everyone in the room sees something -- a name in the roster, a camera tile, and often a message in chat as it joins. This section covers that identity. For making the bot *act* once it is there, see [Controlling the Bot](/docs/meeting/control). ## Roster Name The bot joins as a visible participant with its own entry in the meeting roster. By default it appears as **Meeting Bot**; set `bot_name` (1-100 characters) when you create the session to control the name other attendees see: ```bash curl -X POST https://api.telnyx.com/v2/meeting_sessions \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "meeting_url": "https://meet.google.com/abc-defg-hij", "bot_name": "Q3 Decisions Scribe" }' ``` You can also rename the bot before it joins with `PATCH /v2/meeting_sessions/{id}` while the session is `scheduled`. There is no API for a custom profile picture. The bot uses the meeting platform's default profile for its account, so its roster entry is the name above plus the platform's standard bot appearance. ## Identity What the bot shows on camera is a three-way choice, because the service treats it as one: - **Nothing** -- the default. A plain roster entry with no camera tile. - [**Still image**](/docs/meeting/presence/still-images) -- a static JPEG shown as the bot's camera tile. - [**Digital avatar**](/docs/meeting/digital-avatars) -- a rendered avatar, lip-synced to everything the bot says. An active digital avatar takes precedence over a still image: the image is ignored and its URL is never fetched. A session can hold both, but only the avatar will ever be seen. ## Introductions The bot can announce itself the moment it is admitted, by voice, in chat, or both. Because these are create parameters rather than actions, they are documented with the rest of the create request -- see [Announce the Bot on Arrival](/docs/meeting/join-meeting/introductions). ## Related - [Controlling the Bot](/docs/meeting/control) -- make the bot speak, post to chat, or run an assistant - [Still images](/docs/meeting/presence/still-images) -- a static camera tile for the bot - [Digital avatars](/docs/meeting/digital-avatars) -- render the bot as a speaking avatar on camera - [Announce the Bot on Arrival](/docs/meeting/join-meeting/introductions) -- `speak_on_enter` and `chat_on_enter` - [Join a Meeting](/docs/meeting/join-meeting) -- create a meeting session and send the bot --- ### Still Images > Source: https://developers.telnyx.com/docs/meeting/presence/still-images.md `POST /v2/meeting_sessions` accepts an optional `camera_image`: a static image shown as the bot's camera tile for this session. It is not a native account or participant profile photo, and its exact presentation can vary by meeting platform and recording configuration. Supply exactly one JPEG source -- inline Base64 or an HTTPS URL: ```json { "camera_image": { "format": "jpeg", "base64_data": "" } } ``` ```json { "camera_image": { "format": "jpeg", "url": "https://cdn.example.com/bot-camera.jpg" } } ``` The image contract is strict: - `format` must be `jpeg`. Supplying both `base64_data` and `url`, or neither, is rejected. - `base64_data` must be canonical plain RFC 4648 Base64 -- no data URIs, whitespace, or URL-safe alphabet. The encoded value is limited to 1,835,008 characters and the decoded JPEG to 1,363,148 bytes; the image itself to 4,096 pixels per dimension and 4 megapixels. - `url` must be a public HTTPS URL of at most 2,048 characters with no credentials, fragment, or explicit non-default port. The service fetches it once before creating the bot, with a five-second timeout, no redirects, and a required `2xx` `image/jpeg` response. Signed query strings are allowed -- treat a signed URL as a credential. - `camera_image` is write-only: neither the image bytes nor the source URL are persisted, returned, or logged. If the session also has a [digital avatar](/docs/meeting/digital-avatars), that live output takes precedence -- the static image is ignored and a URL source is not fetched. ## Related - [Meeting Presence](/docs/meeting/presence) -- the bot's roster name and identity choices - [Digital avatars](/docs/meeting/digital-avatars) -- a rendered avatar instead of a static tile - [Join a Meeting](/docs/meeting/join-meeting) -- the rest of the create request --- ### Digital Avatars > Source: https://developers.telnyx.com/docs/meeting/digital-avatars.md Instead of a plain roster entry, give the bot a rendered digital avatar as its in-meeting presence. The digital avatar replaces the bot's camera: attendees see it on screen, and everything the bot says through [speak](/docs/meeting/control/speak) is lip-synced by the digital avatar. This suits branded, customer-facing, or demo meetings where a personified presence matters. Enable it with the `avatar` parameter at session creation. Build the JSON with `jq --arg` so the shell expands your Anam key -- inside a single-quoted `-d '...'` body, `$ANAM_API_KEY` would be sent literally: ```bash REQUEST_BODY=$(jq -n \ --arg meeting_url "https://meet.google.com/abc-defg-hij" \ --arg avatar_id "your-anam-avatar-id" \ --arg anam_api_key "$ANAM_API_KEY" \ '{ meeting_url: $meeting_url, avatar: { provider: "anam", avatar_id: $avatar_id, api_key: $anam_api_key } }') curl -X POST https://api.telnyx.com/v2/meeting_sessions \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ --data "$REQUEST_BODY" ``` | Field | Type | Description | | --- | --- | --- | | `provider` | string | The avatar provider. Today this is `anam`. | | `avatar_id` | string | The digital avatar to use, chosen from your anam account. | | `api_key` | string | Your anam API key. Write-only: it mints a session token and is never stored or returned. Keep it in a secret, not in committed code. | How it works: - Telnyx TTS drives the digital avatar's speech and lip sync. - Avatar sessions are immediate REST creates only: `join_at` is not supported, and avatars are not available through calendar auto-join. Avatar mode is chosen at creation and cannot be toggled mid-meeting. - The digital avatar works on all supported platforms (Google Meet, Zoom, Teams, Webex). - Readiness is visible in the session response via `avatar_state`: `starting`, `connected`, `degraded`, or `disconnected` -- read it rather than assuming the camera is ready. - The avatar's camera video is not included in the provider recording; the bot's audio is. - An active avatar takes precedence over a [static `camera_image`](/docs/meeting/presence/still-images): the image is ignored and its URL is not fetched. The avatar provider is pluggable. `anam` is the only provider available today; additional providers, including a Telnyx-hosted digital avatar, are under evaluation for future releases. ## Related - [Meeting Presence](/docs/meeting/presence) -- the bot's roster name and identity choices - [Still images](/docs/meeting/presence/still-images) -- a static camera tile instead of an avatar - [Telnyx AI Assistants](/docs/meeting/ai-assistants) -- pair the avatar with an assistant so it speaks the assistant's words --- ### Controlling the Bot > Source: https://developers.telnyx.com/docs/meeting/control.md While the meeting is `active`, the bot does three things, and you choose how each is driven: - [**Speak**](/docs/meeting/control/speak) -- say text out loud, and stop mid-utterance. - [**Chat**](/docs/meeting/control/chat) -- post to the meeting's native chat. - [**Automate**](/docs/meeting/ai-assistants) -- hand the *speaking* to a Telnyx AI Assistant, which listens and answers on its own. The first two are manual: every word is a call you make. The third is designed to own the *speaking* -- an attached assistant holds the conversation -- while `send_chat` keeps working exactly as it does without one. `speak` is **not** refused with an assistant attached: the bot becomes a webpage-output bot and speak audio routes through the assistant's output page instead of the bot mic (see [Speak](/docs/meeting/control/speak)). The assistant is still meant to do the talking, so pick between speaking modes before the meeting rather than discovering the difference during it; [Telnyx AI Assistants](/docs/meeting/ai-assistants) sets out what the assistant takes over. All manual actions return `202 Accepted` and complete asynchronously. ## Same Commands over WebSocket The same actions can be sent as frames on the [live transcript stream](/docs/meeting/live-transcript) instead of over REST: ```json { "action": "speak", "text": "Let's recap what we agreed on.", "interrupt": false } ``` ```json { "action": "send_chat", "text": "Noted. I will send the summary after the call." } ``` The stream is convenient for low-latency bots, but it allows at most 8 commands in flight or queued and gives no per-command acknowledgement. Prefer REST actions when you need a durable, acknowledged response. The socket's own limits -- frame size, close codes, and cursor resumption on reconnect -- are covered in [Live Transcript](/docs/meeting/live-transcript#connect-to-the-websocket-stream). ## Errors Action requests fail with a non-2xx status and an error envelope, for example when the session is not `active`: ```json { "error": { "code": "invalid_state", "message": "session must be active to perform this action (current status: ended)" } } ``` Not every failure carries a body. A `502` from [`speak`](/docs/meeting/control/speak) — for instance when the audio cannot be handed to an [assistant](/docs/meeting/ai-assistants) or avatar output page — can come back with no JSON, so branch on the status code before parsing the body. Every code the Meeting API returns is listed in [Meeting API Errors](/docs/meeting/errors). ## Related - [Speak](/docs/meeting/control/speak) -- make the bot say something, and stop it - [Chat](/docs/meeting/control/chat) -- post to the meeting chat and read replies - [Telnyx AI Assistants](/docs/meeting/ai-assistants) -- let an assistant hold the conversation - [Live Transcript](/docs/meeting/live-transcript) -- follow the transcript stream in real time --- ### Speak > Source: https://developers.telnyx.com/docs/meeting/control/speak.md `POST /v2/meeting_sessions/{id}/actions/speak` makes the bot speak text into the meeting. The session's `voice` is the default; override it per request. ```bash curl -X POST https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../actions/speak \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Here are the three decisions from this call.", "interrupt": false }' ``` The full request schema is on [Speak in a meeting session](/api-reference/meeting-session-actions/speak-in-a-meeting-session). Speech is queued, so a long utterance is never cut short by the next `speak` call. If `barge_in` was enabled on the session, participants can interrupt the bot with their own speech. With a [Telnyx AI Assistant](/docs/meeting/ai-assistants) attached, the bot is a webpage-output bot: `speak` is accepted, but the audio routes through the assistant's output page instead of the bot mic and plays only once the assistant is `connected`. If that page is unreachable the call fails with a `502` (see [Errors](/docs/meeting/control#errors)). The assistant is designed to own the conversation, so prefer letting it speak; to have the bot say a fixed line the moment it joins, see [Announce the Bot on Arrival](/docs/meeting/join-meeting/introductions). ## Stop Speaking `POST /v2/meeting_sessions/{id}/actions/stop_speaking` stops the current utterance and flushes the queue. ```bash curl -X POST https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../actions/stop_speaking \ -H "Authorization: Bearer $TELNYX_API_KEY" ``` ## Related - [Controlling the Bot](/docs/meeting/control) -- the WebSocket alternative and the action error envelope - [Chat](/docs/meeting/control/chat) -- post to the meeting chat instead of speaking - [Announce the Bot on Arrival](/docs/meeting/join-meeting/introductions) -- `speak_on_enter` - [Digital avatars](/docs/meeting/digital-avatars) -- lip-sync everything the bot says --- ### Chat > Source: https://developers.telnyx.com/docs/meeting/control/chat.md `POST /v2/meeting_sessions/{id}/actions/send_chat` posts a message to the meeting's native chat as the bot. ```bash curl -X POST https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../actions/send_chat \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "I will send the summary after this call." }' ``` Native chat is supported on Google Meet, Zoom, and Teams. Webex does not support native chat, although joining and audio/video output remain supported. Chat is two-way: messages other participants post are delivered as `chat.message` events, while the bot's own posts echo back as `chat.sent`. Read them on the [WebSocket stream](/docs/meeting/live-transcript) for live delivery, or from the stored [event history](/docs/meeting/events). There is no chat webhook -- [webhooks](/docs/meeting/webhooks) carry session status, transcript completion, recordings and artifacts only. The full request schema is on [Send chat in a meeting session](/api-reference/meeting-session-actions/send-chat-in-a-meeting-session). Unlike [speak](/docs/meeting/control/speak), chat keeps working with a [Telnyx AI Assistant](/docs/meeting/ai-assistants) attached. The assistant owns the voice, not the chat. To post a message automatically the moment the bot joins, see [Announce the Bot on Arrival](/docs/meeting/join-meeting/introductions). ## Related - [Controlling the Bot](/docs/meeting/control) -- the WebSocket alternative and the action error envelope - [Speak](/docs/meeting/control/speak) -- say it out loud instead - [Announce the Bot on Arrival](/docs/meeting/join-meeting/introductions) -- `chat_on_enter` - [Session Events](/docs/meeting/events) -- read `chat.message` and `chat.sent` --- ### Telnyx AI Assistants > Source: https://developers.telnyx.com/docs/meeting/ai-assistants.md Bring a [Telnyx AI Assistant](/docs/inference/ai-assistants/no-code-voice-assistant) into the meeting as the bot's intelligence. The assistant hears the room through the bot, reasons with the model and tools you configured, and responds in its own configured voice -- effectively running your assistant inside the meeting. This turns the bot from a recorder into a participant that can answer questions, take notes, and act on what is discussed. If you intend to use an AI Assistant in a meeting with more than one participant, you will need to do some additional configuration to avoid the Assistant addressing every person speaking. See [Suggested Assistant configuration](#suggested-assistant-configuration). ## How It Works The Meeting service connects your Assistant to the meeting directly. Meeting audio reaches the Assistant, the Assistant's speech is played into the room, and everything in between is handled for you. There is nothing to wire up: supply the Assistant's ID and the service does the rest. Earlier versions required a Call Control connection, a caller ID and a loopback SIP URI to bridge audio to the Assistant. That machinery is gone, and so are the fields — sending them now returns `400 invalid_request`. Attach an assistant with the `assistant` parameter at session creation: ```bash curl -X POST https://api.telnyx.com/v2/meeting_sessions \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "meeting_url": "https://meet.google.com/abc-defg-hij", "bot_name": "AI Assistant", "assistant": { "id": "assistant-6207ab25-b185-478f-b2ef-85159e226727", "audio_gate": "half_duplex" } }' ``` The Assistant must belong to the authenticated customer account. | Field | Type | Description | | --- | --- | --- | | `id` | string | The AI Assistant to attach, from your [Assistants](/docs/inference/ai-assistants/no-code-voice-assistant) configuration. 1-255 characters. | | `audio_gate` | string | How meeting audio reaches the assistant: `half_duplex` (default) or `full_duplex`. `full_duplex` allows participants to interrupt the assistant and **costs significantly more** -- see [Audio Gate](#assistant-parameters). | | `dynamic_variables` | object | Values for the [dynamic variables](/docs/inference/ai-assistants/dynamic-variables) in the Assistant's instructions, greeting, or tools. String-to-string, at most 63 entries. | | `leave_on_end` | boolean | Leave the meeting when the Assistant's conversation ends or fails. `false` by default. | The assistant listens to the meeting, runs its configured model and tools (webhooks, handoff, knowledge bases, MCP servers, and more), and speaks in the voice configured on the Assistant itself -- the session's own `voice` applies to [manual speech](/docs/meeting/control/speak) and is not used while an assistant is attached. Its conversation is captured in the [live transcript](/docs/meeting/live-transcript) and [artifacts](/docs/meeting/collect-results) like any other participant. ## Assistant Parameters Each `assistant` parameter has its own configuration and constraints. Select a tab for details. `id` Required Supply the ID of an existing Telnyx AI Assistant in your account. The Assistant's instructions, model, voice, tools, transcription configuration, and other behavior all come from that Assistant resource. - Supply the resource ID, not the Assistant's display name. - The Assistant must be owned by the authenticated customer account. - Test the Assistant on a normal Voice API call before using it in a meeting. - Do not include an Assistant API key in the request. Meeting obtains a short-lived delegated credential from the authenticated customer context. `audio_gate` Optional `audio_gate` decides how meeting audio reaches the Assistant, and the two options differ in what the Assistant receives -- one mixed stream or one stream per participant. That difference is what makes interruption possible, and it is also what drives the cost. **`half_duplex`** default -- The Assistant receives a single **mixed** stream of the meeting. While Assistant audio is playing, that stream is replaced with silence, and the gate stays closed for 300 ms after the last Assistant audio frame. The Assistant cannot hear itself, and it also cannot be interrupted: speech during the gate, including the 300 ms hangover, is not heard. One stream, so cost does not change with the number of people in the room. **`full_duplex`** -- The Assistant receives a **separate stream per participant**. Nothing is muted, so participants can interrupt the Assistant mid-sentence and it responds. Because the Assistant's own output is not in any participant's stream, it does not hear itself either -- this is genuine barge-in, not the gate switched off. **`full_duplex` costs significantly more, and the increase scales with attendance.** One stream per participant means the per-minute cost is multiplied by the number of participants: a six-person meeting ingests roughly six times the audio of a one-person meeting for the same wall-clock duration. Choose it when interruption genuinely matters to the experience, not as a default. Per-participant audio is metered separately from the flat per-minute rates and will be billed on top of them. `half_duplex` is a coarse audio gate, not acoustic echo cancellation. `leave_on_end` Optional When the Assistant's conversation reaches a terminal state, the bot leaves the meeting: ```json { "assistant": { "id": "assistant-id", "leave_on_end": true } } ``` **Terminal means `ended` or `failed`, deliberately.** A session whose purpose was the Assistant has nothing left to offer once the Assistant is dead, and the alternative is the failure mode this exists to remove: a silent bot sitting in the meeting until somebody notices and removes it by hand. Off by default, so a session that does not ask for it behaves exactly as before -- the bot stays after the Assistant stops, and removing it is your job. - Fires **once**. A second terminal transition does not leave twice. - A leave the meeting platform refuses is logged and the session settles as it otherwise would; the leave is best-effort, not a new failure path. - It does not change how the session ends elsewhere. Normal teardown remains the source of truth, so the session still reaches its usual terminal status. Echoed back as `assistant.leave_on_end`, `false` when unset. `dynamic_variables` Optional One Assistant, many meetings, different facts each time. If the Assistant's instructions, greeting, or tools use [dynamic variables](/docs/inference/ai-assistants/dynamic-variables), supply this meeting's values when you create the session: ```json { "meeting_url": "https://meet.google.com/abc-defg-hij", "assistant": { "id": "assistant-id", "dynamic_variables": { "candidate_name": "Ada Lovelace", "role": "Staff Engineer" } } } ``` They are delivered before the Assistant's first utterance, so a greeting that reads `Hi {{candidate_name}}` is already filled in when the Assistant opens the conversation -- the same guarantee as the [realtime conversation API](/docs/inference/ai-assistants/realtime-conversations#pass-dynamic-variables), where the values ride the opening `session.update` frame. The limits, each rejected with `400 invalid_request`: - At most **63 entries**. - Keys **1-128 characters**. - Values must be **strings**. A number or a nested object is rejected rather than coerced. There is no per-value length cap; the whole map is budgeted in aggregate at **1,047,552 bytes (1023 KiB)**. - `streaming_audio`, `ai_assistant_streaming_audio` and `meeting_session_id` are **reserved**. They toggle provider infrastructure or are set by the service rather than fill a prompt template, so they are refused instead of quietly ignored. Echoed back as `assistant.dynamic_variables`, and `null` when none were supplied. They are fixed for the session: there is no way to change them once the bot is on its way. The `telnyx_` prefix is [reserved for system variables](/docs/inference/ai-assistants/dynamic-variables) and is not the place to put your own, but the Meeting service does not filter it -- so `telnyx_end_user_target` reaches an Edge Compute [dynamic-variables webhook](/docs/edge-compute/guides/ai-assistant-backend) as it would on any other channel. ## Suggested Assistant Configuration Everything above is sent on the meeting session. This section is the other half -- settings on the **Assistant resource itself**, in the [Portal](https://portal.telnyx.com/#/ai/assistants) or through the Assistants API. Nothing here is required to attach an Assistant to a meeting, and nothing here is meeting-specific API surface. ### For meetings with more than one participant A one-to-one call has an obvious turn structure: the caller speaks, the Assistant answers. A meeting does not. People talk to each other, and an Assistant that answers every turn it hears will talk over a discussion it was never part of. **Add the `skip_turn` tool.** It lets the Assistant choose to say nothing on a turn -- in effect, to speak only when spoken to. Without it the Assistant has no way to decline a turn, so it will attempt a response to whatever it just heard. ```json Tools { "type": "skip_turn", "skip_turn": {} } ``` **Then tell the Assistant when to use it.** The tool gives the Assistant the ability to stay quiet; the instructions decide when it should. This is a starting point drawn from testing, not a required form of words -- adapt it to your Assistant's own voice: ```text Instructions The call may include the main user and one or more additional participants. Pay close attention to who is speaking and who they are addressing. If the participants are talking to each other and are not addressing you, use the Skip Turn tool and remain silent. If someone addresses you directly, respond normally. ``` **Give it a name people can use.** There is no wake word. Nothing listens for a trigger phrase and switches the Assistant on -- "addressed directly" is a judgement it makes from the conversation, which is why the instructions above have to describe it. What it answers to is the Assistant's own **name**, set on the Assistant resource. An Assistant named `Weather Assistant` is addressed as "Weather Assistant"; one named `Nyx` is addressed as "Nyx". Nothing extra is wired up for meetings -- name the Assistant and that is the name in the room. So the name is worth choosing for a room rather than for a list. It is spoken aloud by people over compressed audio, so two or three syllables carry better than one, and a name that collides with ordinary meeting speech -- "signal", "echo", "central" -- will pull the Assistant into conversations it was not part of. It is also worth naming in the instructions, so the Assistant recognises itself when somebody says it: ```text Instructions You are in this meeting as Nyx. Participants will address you by that name. ``` How an Assistant handles several speakers at once is not specific to meetings. [Multi-participant calls](/docs/inference/ai-assistants/multi-participant-calls) covers the underlying behaviour and is worth reading alongside this. ### To let the Assistant end its own session **Add the `hangup` tool** if the Assistant should be able to decide it is no longer needed and end the conversation, rather than staying until something else removes it. ```json Tools { "type": "hangup", "hangup": {} } ``` This composes with [`leave_on_end`](#assistant-parameters): the tool ends the Assistant's conversation, and `leave_on_end: true` on the session turns that into the bot leaving the meeting. Set the tool alone and the Assistant stops talking while the bot stays in the room; set both and the bot goes when the Assistant decides it is done. ## Authentication and Resource Ownership For production requests: - The request must pass through the authenticated production Gateway, which supplies an account and actor identity. - The Assistant must belong to that customer. - The Meeting service acts on the customer's behalf using short-lived delegated credentials resolved immediately before each command. - API keys, bearer tokens, and webhook public keys must not be included in the `assistant` object. Unknown fields are rejected. For example, this is invalid: ```json { "assistant": { "id": "assistant-id", "api_key": "***" } } ``` The API response returns `id`, `audio_gate`, `dynamic_variables` and `leave_on_end` — the whole of the assistant configuration. ## Session Restrictions Assistant-backed meeting sessions are currently: - **Immediate-only** -- `join_at` cannot be supplied. - **Incompatible with `barge_in: true`** -- the Telnyx AI Assistant owns interruption behavior. To let participants interrupt the Assistant, set `audio_gate: "full_duplex"` rather than `barge_in`. - **Compatible with an avatar** -- an Assistant and an anam avatar can be used together; Assistant speech is then delivered through the lip-synced avatar output. ## Troubleshooting | Symptom | Typical cause | | --- | --- | | `400 invalid_request` | `id` is empty or over 255 characters; `audio_gate` is not `half_duplex` or `full_duplex`; `dynamic_variables` exceeds 63 entries, has a key outside 1-128 characters, has a non-string value, exceeds the aggregate 1,047,552-byte budget, or uses a reserved key; `barge_in: true` or `join_at` was supplied; or the object carries a field that is not `id`, `audio_gate`, `dynamic_variables` or `leave_on_end` — including `call_control_connection_id`, `from` and `loopback_sip_uri`, which are no longer part of the API. | | `503 not_configured` | Production Assistant support or dedicated Assistant Recall ingress is not configured for the deployment. | | Assistant reaches `connected` but is silent | Check the Assistant itself: test it on a normal Voice API call, and confirm it has a voice configured. | | A greeting or instruction still shows `{{a_variable}}` | No value was supplied for that key. Variables are per session and set only at create time, so check the `dynamic_variables` echoed back on the session rather than the Assistant's own configuration. | | `assistant_state` becomes `failed` | The Assistant could not be started. Confirm the ID is correct and the Assistant belongs to this account. | | The Assistant answers when people are talking to each other | It has no way to decline a turn. Add the `skip_turn` tool and the instructions in [Suggested Assistant configuration](#suggested-assistant-configuration) — this is the most common complaint in meetings with more than two people. | | The Assistant stopped but the bot is still in the meeting | Expected without `leave_on_end`. The Assistant reaching `ended` or `failed` does not remove the bot on its own — set `assistant.leave_on_end: true` at create time, or remove the bot yourself when you see the state change. | | Assistant responds to itself | You are on `full_duplex` with an unusual audio path, or on a build predating it. `half_duplex` prevents it outright, at the cost of mid-speech interruption. | | Participants cannot interrupt the Assistant | Expected on `half_duplex`, including for 300 ms after it stops speaking. Set `audio_gate: "full_duplex"` -- and read the cost note first. | ## Combine with a Digital Avatar The `assistant` and `avatar` parameters are independent, so you can use them together or separately: | Assistant | Avatar | Result | | --- | --- | --- | | yes | no | AI Assistant runs the conversation as a plain roster participant | | yes | yes | AI Assistant runs the conversation, rendered as a [speaking avatar](/docs/meeting/digital-avatars). Either `audio_gate` works | | no | yes | Scripted bot presence rendered as a speaking avatar (see [Digital avatars](/docs/meeting/digital-avatars)) | ## Related - [Multi-participant calls](/docs/inference/ai-assistants/multi-participant-calls) -- how the Assistant behaves when multiple people are in the conversation - [Controlling the Bot](/docs/meeting/control) -- the manual alternative: speak and chat by hand - [Meeting Presence](/docs/meeting/presence) -- the bot's roster name and on-camera identity - [Digital avatars](/docs/meeting/digital-avatars) -- render the bot as a speaking avatar on camera --- ## After the Meeting ### Collect Results > Source: https://developers.telnyx.com/docs/meeting/collect-results.md When the meeting ends, the transcript is finalized and stored. If `summarize_on_end` was set to `true` on session creation, a `summary` artifact is generated via Telnyx Inference (action items are a separate artifact type you request yourself). Read results directly, or have them pushed to a webhook. A terminal `ended` status alone does not prove the bot attended -- check that `joined_at` is non-null when attendance matters. ## Get the Transcript `GET /v2/meeting_sessions/{id}/transcript` returns the finalized transcript, cursor-paged: with no query parameters it returns at most the first 100 segments, not the entire transcript. Page with `after` and `limit` (1-1,000) until a request comes back with empty `data`: Cursor and long-poll query parameters (`after`, `limit`, `wait_seconds`) are documented on [List meeting session transcript](/api-reference/meeting-session-data/list-meeting-session-transcript). ```bash curl -H "Authorization: Bearer $TELNYX_API_KEY" \ "https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../transcript?after=0&limit=1000" ``` ```json { "data": [ { "seq": 41, "text": "let's ship it", "speaker_label": "Ada L.", "confidence": 0.94, "relative_ts": 1240.5, "occurred_at": "2026-06-16T09:00:42Z" } ], "meta": { "next_after": 41 } } ``` The `transcript.completed` webhook fires when the transcript is finalized. See [Webhooks](/docs/meeting/webhooks). ## Artifacts Artifacts are generated outputs read from the transcript. Each one is its own request (`summarize_on_end` attempts only a `summary`). Trigger one, then poll until it is `completed` and read the generated text from `content.text`. An artifact can be requested any time the transcript has content -- including while the meeting is still running; a session with an empty transcript returns `409 invalid_state`. **Every artifact is an inference run, and every request is billed.** Generation happens on Telnyx inference infrastructure rather than as part of the per-minute meeting rates, so each artifact you ask for costs money -- including the automatic one from `summarize_on_end`. Tokens are billed at [Inference API](https://telnyx.com/pricing/inference-api) rates. | Type | What it produces | | --- | --- | | `summary` | The recap of the meeting | | `action_items` | Owned tasks | | `decisions` | Decisions made, with owners where the transcript names them | | `topics` | The themes discussed, each with a one-line note | | `open_questions` | Questions raised but not answered, and unresolved items that are not yet action items | | `custom` | Anything else, answered from a `prompt` you supply | ### Create an Artifact `POST /v2/meeting_sessions/{id}/artifacts` returns `202 Accepted`. The request is not idempotent -- each call generates a new artifact, and each one is billed. Asking for the same type twice produces two artifacts and two charges, so guard the call rather than relying on the service to collapse it. ```bash curl -X POST https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../artifacts \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "summary" }' ``` The full request schema is on [Create a meeting session artifact](/api-reference/meeting-session-artifacts/create-a-meeting-session-artifact). ### Ask Your Own Question `custom` answers an open-ended request from the transcript. It takes a `prompt` of 1-4000 characters, and the prompt is required: ```bash curl -X POST https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../artifacts \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "custom", "prompt": "Which customers were named, and what was promised to each?" }' ``` - `prompt` is **required** when `type` is `custom`, and **rejected with `400`** on any named type. - It is trimmed before storage, and echoed back on every artifact -- `prompt: null` for a named type -- including in the `artifact.completed` webhook payload. A consumer can always see what produced a given output. **Answers are grounded in the transcript alone.** Nothing else is consulted, and where the transcript cannot answer the request the model is instructed to say so explicitly rather than fill the gap. Treat "the transcript does not say" as a valid answer, not a failure. ### List Artifacts ```bash curl -H "Authorization: Bearer $TELNYX_API_KEY" \ "https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../artifacts" ``` ### Get an Artifact ```bash curl -H "Authorization: Bearer $TELNYX_API_KEY" \ "https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../artifacts/mtgart_..." ``` ```json { "data": { "id": "mtgart_4e7c...", "session_id": "mtgsess_9b2f...", "type": "summary", "prompt": null, "status": "completed", "content": { "text": "Decisions: ... Actions: ..." }, "model_provenance": { "model": "example-model", "provider": "telnyx" }, "failure_reason": null, "created_at": "2026-06-16T09:05:10Z", "updated_at": "2026-06-16T09:05:16Z" } } ``` Poll the artifact until `status` is `completed` or `failed`. A `failed` artifact includes a `failure_reason`. The `artifact.completed` and `artifact.failed` webhooks also fire. Generation currently reads at most the first 10,000 transcript segments, so an exceptionally long meeting can produce an incomplete artifact, and model context limits can still cause a failure. An automatic `summarize_on_end` attempt that is skipped (for example, on an empty transcript) creates no artifact row at all. ## Recordings Sessions record automatically, so a recording is coming. It is not ready the moment the meeting ends, though -- the provider still has to process it, and this endpoint returns an empty `data` array until it does. Wait for the `recording.available` webhook rather than assuming `data[0]` exists, or poll if you are not receiving webhooks. `GET /v2/meeting_sessions/{id}/recordings` returns the available recordings with short-lived download URLs: ```bash curl -H "Authorization: Bearer $TELNYX_API_KEY" \ "https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../recordings" ``` ```json { "data": [ { "type": "video_mixed", "url": "https://...", "expires_at": null } ] } ``` The full response schema is on [List meeting session recordings](/api-reference/meeting-session-data/list-meeting-session-recordings). `url` is a short-lived signed link on the recording provider's storage; re-fetch this endpoint whenever you need a current URL rather than treating it as durable storage. The current adapter returns `expires_at: null` -- the API does not expose the URL's actual expiry, and the link still expires. The `recording.available` webhook fires when a recording is ready; it carries only `recording_types` and never URLs, so always fetch them from this endpoint. ### Delete Recording Media `DELETE /v2/meeting_sessions/{id}/recording_media` permanently deletes the provider-hosted recording media for the session (recordings, audio, video, and debug media). This is irreversible and returns `202 Accepted`: ```bash curl -X DELETE https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../recording_media \ -H "Authorization: Bearer $TELNYX_API_KEY" ``` ```json { "data": { "meeting_session_id": "mtgsess_9b2f...", "provider": "recall", "scope": "provider_recording_media", "deletion_status": "requested" } } ``` `provider` identifies the media host (for example `recall`). `deletion_status` is `requested` or `already_in_progress`. Deletion removes the provider-hosted media only -- the session, its transcript, events, and artifacts remain intact. ## Related - [Join a Meeting](/docs/meeting/join-meeting) -- create a meeting session and send the bot - [Live Transcript](/docs/meeting/live-transcript) -- follow the transcript stream in real time - [Webhooks](/docs/meeting/webhooks) -- receive session events on your own endpoint --- ## Integration ### Events > Source: https://developers.telnyx.com/docs/meeting/events.md Everything a session does -- joining, admission, speech starting and stopping, chat, recordings, artifacts -- is recorded as an ordered event with a monotonic `seq`. The same events reach you three ways, and they do not carry the same coverage: | | Delivery | Carries | | --- | --- | --- | | `GET /events` | You poll | Every event type below, including after the meeting ends | | [Webhooks](/docs/meeting/webhooks) | Telnyx pushes | Five summary events -- see the table on that page | | [WebSocket stream](/docs/meeting/live-transcript) | Telnyx pushes, live | Every event type, live, plus transcript catch-up | The stored history is the authoritative record. A webhook that never arrived, a socket that dropped mid-meeting, or a process that restarted are all recovered the same way: read from the last `seq` you durably stored. ## Read the Event History `GET /v2/meeting_sessions/{id}/events` returns events in `seq` order. ```bash curl "https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../events?limit=100" \ -H "Authorization: Bearer $TELNYX_API_KEY" ``` ```json { "data": [ { "seq": 12, "type": "session.status_changed", "payload": { "status": "active", "previous_status": "waiting_for_admission" }, "occurred_at": "2026-06-16T09:00:05Z" } ] } ``` Every event has the same four fields: `seq`, `type`, `payload`, and `occurred_at`. The shape of `payload` is determined by `type`. ### Paging and Resuming Pass `after` with the highest `seq` you have already processed, and `limit` for the page size. Because `seq` is monotonic within a session, the same parameter serves both jobs -- paging through a completed session and resuming after an interruption are the same request. ```bash curl "https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../events?after=12&limit=100" \ -H "Authorization: Bearer $TELNYX_API_KEY" ``` Store `seq` only after the event is processed. Storing it on receipt means an event lost to a crash is never re-read, and there is no way to detect the gap afterwards. ## Event Types Not every type is delivered by every mechanism -- webhooks carry the five summary events listed on the [Webhooks](/docs/meeting/webhooks) page, while `GET /events` and the stream carry all of them. | Event | Meaning | | --- | --- | | `session.created` | The session record exists; the bot has not necessarily joined. | | `session.status_changed` | Lifecycle moved -- carries `status` and `previous_status`. | | `participant.join` | Someone joined the meeting. | | `participant.leave` | Someone left. | | `participant.speech_on` | A participant started speaking. | | `participant.speech_off` | A participant stopped speaking. | | `transcript.segment` | One transcribed segment. Never delivered by webhook. | | `transcript.completed` | The transcript is finalized -- carries `segment_count` and `last_seq`. | | `bot.speak_requested` | A `speak` action was accepted. | | `bot.speak_stopped` | Speech was stopped, by `stop_speaking` or by barge-in. | | `bot.speak_on_enter_delivered` | The configured `speak_on_enter` line was spoken. | | `bot.chat_on_enter_delivered` | The configured `chat_on_enter` message was posted to the meeting chat. | | `chat.message` | A participant posted in the meeting chat. | | `chat.sent` | The bot's own chat message was posted. | | `recording.available` | A recording is ready -- carries `recording_types`, never URLs. | | `artifact.completed` | An artifact finished -- carries `content.text`. | | `artifact.failed` | An artifact could not be generated. | | `avatar.connected` | The avatar media channel attached. | | `avatar.degraded` | The avatar is running in a reduced state. | | `avatar.disconnected` | The avatar media channel dropped. | Treat this list as open. The event type field is deliberately extensible, and a client that rejects an unrecognized `type` will break the first time a new one is added -- ignore what you do not recognize rather than failing. The complete machine-readable enum, with the exact payload schema bound to each type, is in the [Meeting Session agent stream reference](/api-reference/websockets/meeting-session-agent-stream). ## Choosing a Mechanism - **Webhooks** for "tell me when it is finished" -- a summary arrives without you holding a connection, and the five events cover the end states most integrations act on. - **The stream** for anything live: transcript as it is produced, speech starting and stopping, participants arriving. - **`GET /events`** for everything else, and as the backstop for both. It is the only mechanism that answers "what did I miss" after the fact, so a reliable integration reconciles against it even when it is driven by pushes. ## Related - [Webhooks](/docs/meeting/webhooks) -- push delivery, envelope, and signature verification - [Live Transcript](/docs/meeting/live-transcript) -- the WebSocket stream and transcript polling - [Collect Results](/docs/meeting/collect-results) -- the finalized transcript, artifacts, and recordings --- ### Webhooks > Source: https://developers.telnyx.com/docs/meeting/webhooks.md Instead of polling, point the bot at your own endpoint. Set `webhook_url` when creating the session and Telnyx pushes session events to it. The destination must be a public HTTPS URL without embedded credentials and must not resolve to loopback, private, link-local, or otherwise reserved addresses; redirects are not followed. Each session returns a `webhook_secret` exactly once at creation -- store it; it is not retrievable again. ## Envelope and Events Every delivery is an HTTP `POST` with `Content-Type: application/json`, no authorization header, and one envelope shape -- deduplicate on `id`: ```json { "id": "whdel_9f2c...", "event": "session.status_changed", "version": "1", "occurred_at": "2026-06-16T09:00:05Z", "data": {} } ``` | Event | `data` fields | | --- | --- | | `session.status_changed` | `session_id`, `status`, `status_detail`, `recording` | | `transcript.completed` | `session_id`, `segment_count`, `last_seq`, `ended_at` | | `recording.available` | `session_id`, `recording_types` (never URLs) | | `artifact.completed` | `session_id`, `artifact_id`, `type`, `prompt` (the question a `custom` artifact answered, `null` for a named type), `content: {text}`, `model_provenance` | | `artifact.failed` | `session_id`, `artifact_id`, `type` | Each event's full payload schema lives in the **Webhooks** group of the API Reference. Individual `transcript.segment` messages are never delivered over webhooks. Read segments via the [transcript endpoint](/docs/meeting/live-transcript) or the stream. ## Verify Signatures Each delivery is signed with an `X-Meeting-Bot-Signature` header: ``` X-Meeting-Bot-Signature: t=1784297100,v1=5e8f...a3c1 ``` Compute HMAC-SHA256 over the timestamp's ASCII bytes, a literal `.` byte, and the exact raw request-body bytes, then hex-encode the result and compare it against `v1`. Do not parse and reserialize the JSON before verifying. ```python import hashlib import hmac header = "t=1784297100,v1=5e8f...a3c1" raw_body = request.body # exact bytes as received t = header.split(",")[0].split("=")[1] v1 = header.split(",")[1].split("=")[1] expected = hmac.new( webhook_secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256, ).hexdigest() verified = hmac.compare_digest(v1, expected) ``` Concatenate the timestamp and body as bytes. Formatting `raw_body` into a string (for example with an f-string) produces `b'...'` in the signed payload and the comparison always fails. Use a constant-time comparison and reject deliveries whose timestamp is outside your accepted clock skew window. ## Delivery and Retry - Up to 5 delivery attempts. Non-2xx responses, redirects, network errors, and timeouts all consume an attempt. - Retries back off: roughly a minute after the first failed attempt, doubling each retry (about `60s * 2^n`). - Each attempt has a 10-second timeout, so respond with a 2xx promptly. - Events are delivered with best effort and duplicates can occur -- including when your endpoint processed an event but its response was lost. Deduplicate on the event `id` and fall back to REST polling when guaranteed receipt matters. ## Related - [Session Events](/docs/meeting/events) -- the full event history, and what to read when a delivery is missed - [Join a Meeting](/docs/meeting/join-meeting) -- set `webhook_url` when creating a session - [Collect Results](/docs/meeting/collect-results) -- read the finalized transcript and artifacts --- ### API Errors > Source: https://developers.telnyx.com/docs/meeting/errors.md Meeting endpoints return failures as a non-2xx status with a single `error` object -- branch on `error.code` and use `error.message` for diagnostics: ```json { "error": { "code": "not_found", "message": "meeting session not found" } } ``` Authentication, permission, rate-limit, and other request failures that occur before reaching the Meeting service use the [general API error codes](/development/api-fundamentals/api-errors) with the standard plural envelope -- a missing or invalid API key, for example, returns `401` with `{"errors": [{"code": "10009", ...}]}`, not the single-`error` shape below. Authenticated operations can also return `403`, `429` (with `Retry-After` when authentication is overloaded), or `503`. A small number of gateway-level failures (such as a `502` for a meeting URL the provider rejects outright) return a plain-text body with no JSON envelope at all, so parse error responses defensively. ## Meeting Error Codes | Code | HTTP | Title | Detail | Action | | --- | --- | --- | --- | --- | | `unauthorized` | `401` | Invalid or missing credentials | Service-level authentication failure. In practice the gateway rejects a missing or invalid key first with the general `10009` error, so this code is rarely seen. | Send your Telnyx API key as a bearer token in the `Authorization` header (exact `Bearer` capitalization). | | `not_found` | `404` | Meeting session not found | No session with the supplied id belongs to the authenticated account. | Verify the session id and that it was created by the same account. | | `invalid_request` | `400`, `413` | Request rejected | A field in the request is invalid or not allowed, for example a disabled `webhook_url`. A request that exceeds a documented size limit returns the same code with `413`. | Correct the field named in `message` and retry. | | `invalid_state` | `409` | Operation not valid for the session status | The operation requires a different lifecycle status, for example only `scheduled` sessions can be updated and actions require an `active` session. | Read the session status first and retry when the session reaches the required state. | | `unsupported_capability` | `422` | Capability not supported on this platform | The requested capability, such as avatar video, is not available on the detected meeting platform. | Check the platform's capabilities before enabling the feature, or omit it. | | `not_configured` | `503` | Feature not configured | A required integration is not configured for the account. | Complete the feature's setup for your account before retrying. | | `tts_error` | Text-to-speech failure | The speak operation failed in the upstream text-to-speech service. | Retry the speak action; if the failure persists, try a different `voice`. | | `provider_error` | Provider failure | The upstream meeting provider failed, for example a recording-media deletion whose outcome is unknown. | Retry the operation and confirm the result with a follow-up read. | | `auth_unavailable` | Authentication service unavailable | The authentication backend could not be reached. | Retry with backoff. | | `internal_error` | Internal server error | An unexpected failure inside the Meeting service. | Retry with backoff; contact support if the failure persists. | ## Related - [Join a Meeting](/docs/meeting/join-meeting) -- create a meeting session and send the bot - [Controlling the Bot](/docs/meeting/control) -- actions that require an `active` session --- ### MCP Server > Source: https://developers.telnyx.com/docs/meeting/mcp.md The Meeting MCP server gives AI agents a dedicated interface for joining meetings, following transcripts and events, interacting in the room, collecting results, and managing calendar auto-join. ## Endpoint and Authentication Connect to: ```text https://api.telnyx.com/v2/meeting_bot/mcp ``` This is the product-specific Meeting MCP server. It is separate from the generic Telnyx API MCP endpoint at `https://api.telnyx.com/v2/mcp`, which exposes broader API actions. The Meeting endpoint uses stateless Streamable HTTP. Send JSON-RPC 2.0 requests with these headers: ```text Authorization: Bearer Content-Type: application/json Accept: application/json, text/event-stream ``` Every request is independent. The server does not issue an `Mcp-Session-Id`, and clients must not depend on state from an earlier `initialize` request. Responses are currently JSON, while clients must still advertise support for both JSON and server-sent events in `Accept`. ## Configure an MCP Client MCP client configuration formats vary. For clients that accept an `mcpServers` map and environment-variable interpolation, use: ```json { "mcpServers": { "telnyx-meeting": { "url": "https://api.telnyx.com/v2/meeting_bot/mcp", "headers": { "Authorization": "Bearer ${TELNYX_API_KEY}" } } } } ``` Set `TELNYX_API_KEY` in the environment that launches the client. If your client does not interpolate environment variables in JSON, use its secret or header configuration instead of storing an API key in a shared configuration file. ## Discover and Call Tools with curl Export a [Telnyx API key](https://portal.telnyx.com/#/app/api-keys) before running the examples: ```bash export TELNYX_API_KEY='' ``` Initialize the MCP connection: ```bash curl --fail-with-body --silent --show-error \ --request POST \ --url 'https://api.telnyx.com/v2/meeting_bot/mcp' \ --header "Authorization: Bearer $TELNYX_API_KEY" \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --data '{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "curl", "version": "1.0.0"} } }' ``` Discover the current tool names, descriptions, and input schemas: ```bash curl --fail-with-body --silent --show-error \ --request POST \ --url 'https://api.telnyx.com/v2/meeting_bot/mcp' \ --header "Authorization: Bearer $TELNYX_API_KEY" \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --data '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' ``` Call the read-only `list_sessions` tool: ```bash curl --fail-with-body --silent --show-error \ --request POST \ --url 'https://api.telnyx.com/v2/meeting_bot/mcp' \ --header "Authorization: Bearer $TELNYX_API_KEY" \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --data '{ "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "list_sessions", "arguments": {}} }' ``` Tool results contain one text content block whose `text` value is JSON. Check `result.isError` before parsing `result.content[0].text`: tool-level failures normally return HTTP `200` with `isError: true`, while authentication and transport failures use HTTP error statuses. JSON-RPC validation failures can instead use the top-level `error` field. ## Available Tools The server currently advertises 19 tools. Use `tools/list` as the source of truth for their current input schemas. ### Meetings and History - `join_meeting` -- create or schedule a meeting session - `get_session` and `list_sessions` -- inspect one session or list sessions - `get_transcript` and `get_events` -- read transcript segments and stored events - `leave_meeting` -- leave or cancel without deleting session history - `get_recordings` -- list session recordings ### In-meeting Actions and Artifacts - `speak`, `stop_speaking`, and `send_chat` -- interact in an active meeting - `create_artifact`, `get_artifact`, and `get_artifacts` -- generate and retrieve summaries, action items, decisions, topics, open questions, or a `custom` answer to a prompt of your own ### Calendar Auto-join - `connect_calendar`, `list_calendar_connections`, and `disconnect_calendar` -- manage calendar connections - `set_calendar_policy` -- configure connection-level auto-join behavior - `list_calendar_meetings` -- list meetings from a connected calendar - `set_meeting_auto_join` -- override auto-join for one calendar meeting ## REST-only Capabilities MCP covers the core Meeting workflow but does not expose every REST or streaming operation: - Updating an existing scheduled session is REST-only; MCP has no equivalent of `PATCH /meeting_sessions/{id}`. - Retrieving one calendar connection directly is REST-only; MCP lists connections but has no single-connection retrieval tool. - The customer WebSocket stream is not exposed through MCP. Use the [Live Transcript](/docs/meeting/live-transcript) guide for long polling and WebSocket access. - `join_meeting` does not accept the REST `avatar` configuration. Digital avatar configuration is REST-only. `camera_image` is supported by both REST and MCP. - `join_meeting` does not accept `assistant`, so attaching a [Telnyx AI Assistant](/docs/meeting/ai-assistants) -- and with it `audio_gate` and `dynamic_variables` -- is REST-only. For these operations, use the [Meeting API reference](/api-reference/meeting-sessions/create-a-meeting-session) alongside MCP. ## Related - [Meeting Quick Start](/docs/meeting/quick-start) -- complete the same core workflow with REST - [Join a Meeting](/docs/meeting/join-meeting) -- scheduling, calendar auto-join, and lifecycle details - [Controlling the Bot](/docs/meeting/control) -- speaking and chat over REST - [Meeting Presence](/docs/meeting/presence) -- roster name, still images, and digital avatars - [Collect Results](/docs/meeting/collect-results) -- transcripts, artifacts, and recordings --- ## API Reference (Meeting (Beta)) ### Meeting Sessions - [Create a meeting session](https://developers.telnyx.com/api-reference/meeting-sessions/create-a-meeting-session.md): Creates a new meeting session. When an idempotency_key is supplied in the request body, replay lookup is scoped to the authenticated account and compares only… - [List meeting sessions](https://developers.telnyx.com/api-reference/meeting-sessions/list-meeting-sessions.md): Returns a list of meeting sessions, optionally filtered by status. - [Retrieve a meeting session](https://developers.telnyx.com/api-reference/meeting-sessions/retrieve-a-meeting-session.md): Retrieves a single meeting session by ID. A session that does not exist or that belongs to a different account both return 404. - [Update a meeting session](https://developers.telnyx.com/api-reference/meeting-sessions/update-a-meeting-session.md): Updates mutable properties of a meeting session. Only sessions in the scheduled state can be updated; any other state returns 409 with the invalid_state error… - [Delete a meeting session](https://developers.telnyx.com/api-reference/meeting-sessions/delete-a-meeting-session.md): Stops a meeting session without deleting its persisted record. Scheduled bots are cancelled, while bots that are joining or active are asked to leave. The pers… ### Meeting Session Actions - [Speak in a meeting session](https://developers.telnyx.com/api-reference/meeting-session-actions/speak-in-a-meeting-session.md): Sends audio / text-to-speech into a meeting session. With a Telnyx AI Assistant (or avatar) attached, the bot is a webpage-output bot: the speak audio routes t… - [Stop speaking in a meeting session](https://developers.telnyx.com/api-reference/meeting-session-actions/stop-speaking-in-a-meeting-session.md): Stops any active text-to-speech playback in a meeting session. - [Send chat in a meeting session](https://developers.telnyx.com/api-reference/meeting-session-actions/send-chat-in-a-meeting-session.md): Sends a chat message into a meeting session. ### Meeting Session Data - [List meeting session events](https://developers.telnyx.com/api-reference/meeting-session-data/list-meeting-session-events.md): Returns stored events ordered by ascending `seq`. To continue, pass the last returned item's `seq` as `after`. An empty page means no later stored events exist… - [List meeting session transcript](https://developers.telnyx.com/api-reference/meeting-session-data/list-meeting-session-transcript.md): Returns transcript segments ordered by ascending `seq`. Default `limit` is 100 and maximum is 1,000. Continue with `after=meta.next_after`. A long-poll timeout… - [List meeting session recordings](https://developers.telnyx.com/api-reference/meeting-session-data/list-meeting-session-recordings.md): Returns recordings for a meeting session. - [Delete meeting session recording media](https://developers.telnyx.com/api-reference/meeting-session-data/delete-meeting-session-recording-media.md): Irreversibly requests deletion of provider-hosted aggregate recording media under the provider contract. The operation retains the Telnyx-local Meeting session… ### Meeting Session Artifacts - [Create a meeting session artifact](https://developers.telnyx.com/api-reference/meeting-session-artifacts/create-a-meeting-session-artifact.md): Requests asynchronous generation of one artifact: `summary`, `action_items`, `decisions`, `topics`, `open_questions`, or `custom`. Each request produces one ar… - [List meeting session artifacts](https://developers.telnyx.com/api-reference/meeting-session-artifacts/list-meeting-session-artifacts.md): Returns a list of artifacts for a meeting session. - [Retrieve a meeting session artifact](https://developers.telnyx.com/api-reference/meeting-session-artifacts/retrieve-a-meeting-session-artifact.md): Retrieves a single meeting session artifact by ID. ### Meeting Session Webhooks - [session.status_changed](https://developers.telnyx.com/api-reference/meeting-session-webhooks/session-status_changed.md): Sent when the session moves to a new lifecycle status. Deliveries are best effort and not guaranteed: up to 5 attempts with backoff that starts around 60 secon… - [transcript.completed](https://developers.telnyx.com/api-reference/meeting-session-webhooks/transcript-completed.md): Sent when the meeting transcript is finalized and ready to read. Individual `transcript.segment` messages are never delivered over webhooks; read segments via… - [recording.available](https://developers.telnyx.com/api-reference/meeting-session-webhooks/recording-available.md): Sent when a recording is ready to download. The payload carries only `recording_types`, never URLs; fetch short-lived download URLs from `GET /meeting_sessions… - [artifact.completed](https://developers.telnyx.com/api-reference/meeting-session-webhooks/artifact-completed.md): Sent when an artifact finishes generating -- `summary`, `action_items`, `decisions`, `topics`, `open_questions` or `custom`. Deliveries are best effort and not… - [artifact.failed](https://developers.telnyx.com/api-reference/meeting-session-webhooks/artifact-failed.md): Sent when an artifact generation fails. Deliveries are best effort and not guaranteed: up to 5 attempts with backoff that starts around 60 seconds and roughly…