Base URL & auth
| Hosted | https://api.cloud.vexa.ai |
| Self-hosted | http://localhost:18056 (the gateway; API_GATEWAY_HOST_PORT, default 18056) |
Platforms
Pass one of these asplatform:
| Platform | platform value |
|---|---|
| Google Meet | google_meet |
| Zoom | zoom |
| Microsoft Teams | teams |
Meeting statuses
A meeting is one record from plan to transcript:| Phase | status values | Who owns it |
|---|---|---|
| Planned (intent) | idle (no time) · scheduled (time set) | you — freely editable |
| Live (bot FSM) | requested · joining · awaiting_admission · needs_help · active · stopping | the bot lifecycle |
| Terminal | completed · failed | — |
PATCH/DELETE work only while the
record is still planned; once the bot lifecycle owns it they answer 409.
Plan a meeting
Create a meeting before it happens — no bot is spawned. All fields are optional: a plan can be just a title, and the link can be attached later.What the meeting is about — shown in the Meetings list.
A Meet/Zoom/Teams link — parsed server-side into
platform + native_meeting_id. Unrecognized links are rejected with 422.Bind the meeting to a shared workspace — its members then see the meeting, its live feed, and the transcript.
Send the bot automatically at start time. Default
true.POST /meetings
201 with the meeting record. 409 when a non-terminal meeting already exists for the
same link.
Edit or delete a plan
Planned meetings are addressed by record id (a plan without a link has no platform/native path). Send only the fields you’re changing;null clears a field (scheduled_at: null flips the
status back to idle, meeting_url: null detaches the link, workspace_id: null unbinds).
PATCH /meetings/{meeting_id}
DELETE /meetings/{meeting_id}
404 for a record you don’t own and 409 once the bot lifecycle owns the record.
Auto-join
Ascheduled meeting with a link is joined automatically: a background sweep sends the bot
~60 s before scheduled_at (never more than 10 min after — a stale plan is skipped, not joined
late). Opt out per meeting with auto_join: false. Failures are loud: a concurrency-cap or spawn
failure stamps auto_join_error into the meeting’s data and retries after a backoff. Timing is
tunable on a self-host — see Configuration.
Calendar sync
Connect a calendar’s secret ICS address and upcoming meetings import as planned records automatically (only events carrying a recognizable Meet/Zoom/Teams link; one record per event — the next occurrence of a recurring series). The URL is a secret: read-backs return it masked.auto_join here is the global default stamped onto imported meetings.
PUT /user/calendar
GET /user/calendar
Response
/calendar/embed)
is rejected with a 422 that points at the right field — the Secret address in iCal format
(on Google Workspace domains an admin policy can hide that field; see
Calendar sync for the unlock).
Sync feedback — connecting from the Terminal runs a sync immediately; over the API the same
two edges are yours:
POST /user/calendar/sync — run the sync NOW, get the result
Response
GET /user/calendar/sync — the last sync's status
last_error, when set, is a human-readable reason (wrong-URL kind, HTTP status, oversize,
redirect, not-ICS content) — the same strings the Terminal panel shows. 404 on the POST means no
feed is connected; 503 means the deployment has calendar sync disabled (see
Configuration).
Disconnect with {"ics_url": null}. See Calendar sync for where to find
the secret address.
Send a bot to a meeting
google_meet · zoom · teamsThe meeting id from the join URL (e.g.
abc-defg-hij).Display name the bot uses in the call. Defaults to
Vexa.ISO code (e.g.
en); omit to auto-detect.transcribe (default) or translate.POST /bots
requested, keeping its title, scheduled_at, and workspace binding. 409 when a bot is
already active on that link; 429 past your concurrency limit.
Get the transcript
GET /transcripts/{platform}/{native_meeting_id}
Response
completed: false and are replaced by completed: true
confirmations.
Manage the bot
Update config — PUT /bots/{platform}/{native_meeting_id}/config
Stop / leave — DELETE /bots/{platform}/{native_meeting_id}
Running bots — GET /bots/status
Make the bot speak — POST /bots/{platform}/{native_meeting_id}/speak
Config (
PUT …/config, change language/task mid-call) and speak (POST …/speak, TTS into the
call) ride the live bot-control plane and are not yet wired in the v0.12 open-core stack — they currently
return 404. Send-a-bot, stop, running bots (GET /bots/status), list, and transcripts are live.List meetings — GET /meetings
Single meeting — GET /meetings/{meeting_id}
Speaker-attributed transcripts
Each segment is diarized — attributed to a speaker (a bound display name, or a provisional label until it binds) — with word-level timestamps (words[]) and a confidence. Speaker attribution is
text-level (who said what), via speaker binding / clustering / captions — not separate audio tracks.
Times are seconds from session start; absolute_start_time / absolute_end_time give wall-clock.
The same segments arrive live (completed: false, a pending draft) and then confirmed
(completed: true) — the gateway forwards the confirmed-plus-pending bundle to subscribers as the meeting
runs.
Recordings
The meeting’s audio recording is uploaded to object storage — on a self-host, your own MinIO bucket, so it never leaves your environment. This is the meeting audio, stored separately from the diarized transcript above (there is no “per-speaker audio” — speaker separation lives in the transcript as text).List recordings — GET /recordings
Recording detail — GET /recordings/{recording_id}
Master metadata (finalize-on-read) — GET /recordings/{recording_id}/master?type=audio
raw_url pointing at the byte stream
GET /recordings/{recording_id}/media/{media_file_id}/raw, which the player loads.
In Vexa’s runtime terms, a bot is a browser container; the transcript it produces compiles into the workspace, where agents act on it. See Meetings.