Skip to main content
A Vexa bot joins a call like any participant and streams back a speaker-attributed transcript. No plugin, no host configuration.

Get an API key

There are two ways to run Vexa. Both speak the same API; only the base URL differs.

Hosted

  1. Sign in at vexa.ai/signin with a Google account.
  2. Copy your API key from your account page.
  3. Send a bot:
native_meeting_id is the id inside the join URL — abc-defg-hij in https://meet.google.com/abc-defg-hij. New accounts get **5offreebotcreditandnocreditcardisrequiredabout16hoursofbottimeat5 of free bot credit and no credit card is required** — about 16 hours of bot time at 0.30/hr (pricing).

Self-hosted

Vexa is Apache-2.0 and runs entirely inside your own perimeter. make all pulls the published images and prints an API key when the stack is up:
Full walkthrough: Quickstart. Base URLs, scopes, and key rotation: Authentication.

Set your base URL

The rest of this page uses $API_BASE and $API_KEY:

1. Send the bot

The only thing that changes between platforms is the platform value.
native_meeting_id is the id inside the join URL (e.g. abc-defg-hij for Meet, the room name for Jitsi), not the whole URL. Have only the URL? Send meeting_url alone — the API parses it to extract platform, native_meeting_id, and (for Zoom/Teams links that embed one) the passcode:
A URL that isn’t a recognizable meeting link is refused with a 422 naming the missing native_meeting_id; if you supply both platform and meeting_url and they disagree, that’s a 422 too. An explicit native_meeting_id always wins — the URL never overrides it. Optional: language (ISO code) and task (transcribe default, or translate). Language works in two modes:
  • Auto (omit language) — the STT unit detects the language of each transcription window independently, and every segment carries the detected code in its language field.
  • Forced (language: "es") — every STT call is pinned to that language; every segment carries it.
Detection is per window (one STT call, a few seconds of one speaker’s audio), not per word: code-switching within a window takes the window’s language; switching between utterances lands on the segment boundary. A window whose language could not be determined has language: null.

Capture-only (no STT)

Default spawns require a configured transcription backend. Without TRANSCRIPTION_SERVICE_URL / TRANSCRIPTION_SERVICE_TOKEN, POST /bots answers 503 (refuse loud) rather than joining a bot that can never produce text. For a deliberate no-STT meeting, opt out on the spawn:
Or set TRANSCRIBE_ENABLED=false in the deployment env so every spawn defaults to capture-only. Recording is separate: pass recording_enabled / RECORDING_ENABLED if you also need persisted audio — audio only; there is no video recording path. See Configuration. On Meet and Teams the bot may wait in the lobby until a host admits it; a Jitsi room with the lobby enabled behaves the same way.
You often don’t need this call at all: a planned meeting with a time and a link auto-joins at start, and calendar sync plans them for you. Sending a bot to a link you already planned upgrades the same meeting record (title and workspace binding survive) — it never creates a duplicate.

2. Read the transcript

Segments stream in as the meeting runs. Poll:
Each segment is diarized (who said what) with word-level timestamps. Live drafts arrive as completed: false and are replaced by completed: true confirmations. For live, per-segment push instead of polling, subscribe over WebSocket — see the Meetings API.

3. Manage and stop

Mid-call reconfiguration (PUT …/config, change language/task while the bot is in the call) rides the live bot-control plane and is not yet wired in the v0.12 open-core stack — it currently returns 404. Set language and task on the POST /bots spawn. See the Meetings API.
The audio recording lands in your object storage — retrieve it via GET /recordings (Meetings API).

Authenticated mode

When authenticated: true is set in the request, the bot uses a persistent browser profile to join the call as your Google account. If the profile is signed out (session expired, cookies cleared), the bot refuses to join with an auth_session_missing error — it never silently degrades to an anonymous join. Re-authenticate the browser profile and retry.

When the bot leaves on its own

An active bot leaves after it receives no remote microphone audio for 10 minutes and the meeting finishes as completed(left_alone). The same rule covers an empty room and a room containing only silent notetakers: it listens to the remote-audio signal and does not identify participants by name. Bot-generated speech is not part of that signal. Set a per-meeting window with automatic_leave.max_time_left_alone (milliseconds):
An explicit stop still leaves immediately. Removal by the meeting host finishes as evicted, and the four-hour active-phase cap remains a backstop. A participant who stays but says nothing for the entire silence window will also be left; increase the window when silent attendance is expected. Self-hosters can change the deployment fallback with BOT_ALONE_SILENCE_WINDOW_MS; an explicit per-meeting value wins.

Next

The transcript compiles into your workspace, where agents act on it. Turn that into an automatic write-up: Report after every meeting.
If the bot joins but no text appears, transcription isn’t configured — see Troubleshooting.