API reference
Spin up video, voice, live-stream and broadcast sessions, then pull back structured AI notes and tasks — the same data your team sees in-app. JSON in, JSON out, over HTTPS.
Base URL
Every endpoint below is relative to a versioned base URL. Use the staging host while you build, then flip a single constant to go live.
Live traffic, real minutes billed.
Sandbox for integration testing — never billed, sessions expire in ~5 minutes.
Use your same key from the dashboard on either host. Sessions created against staging are marked test, expire after ~5 minutes, and are excluded from your usage and cost.
Authentication
Pass your secret key as a bearer token on every request. Generate and rotate keys in the dashboard. Never ship a secret key in client-side code.
Authorization: Bearer vk_live_9f2a…Quick start
Create your first session in one call. Copy, paste in your key, run.
curl https://api.vanillameet.com/v1/sessions \
-H "Authorization: Bearer $VANILLA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"medium": "video",
"mode": "group",
"capacity": 6,
"recording": true
}'const res = await fetch("https://api.vanillameet.com/v1/sessions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VANILLA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ medium: "video", mode: "group", capacity: 6 }),
});
const session = await res.json();
console.log(session.join_url);Create a session
Returns a single shareable join link, capped by capacity. Attach any metadata and it comes back on every related webhook.
POST /v1/sessions
Authorization: Bearer <api_key>
{
"medium": "video" | "voice" | "live_stream" | "broadcast",
"mode": "1:1" | "group",
"capacity": 6,
"recording": false,
"metadata": { "reference_id": "abc123" },
"features": { "screen_share": false, "recording": false }
}
200 OK
{
"session_id": "sess_9f2a…",
"join_url": "https://vanillameet.com/j/9f2a",
"expires_at": "2026-07-24T18:30:00Z"
}Meeting features
Turn in-call actions on or off per meeting with an optional features object on POST /v1/sessions. Any key you omit stays enabled — pass false to hide that control for everyone in the call.
screen_sharePresent / share screenchatIn-call text chatrecordingRecord the callcaptionsLive captionsreactionsEmoji reactionsraise_handRaise handcelebrateCelebrate 🎉inviteInvite others mid-call{
"medium": "video",
"features": { "screen_share": false, "recording": false }
}List sessions
Filter your session history by date range and medium. Results are paginated newest-first.
GET /v1/sessions?since=2026-07-01&until=2026-07-24&medium=video
200 OK
{
"data": [
{ "session_id": "sess_9f2a…", "medium": "video", "minutes": 42, "ended_at": "2026-07-20T15:04:00Z" }
],
"has_more": false
}Get notes for a session
Structured AI notes and the per-participant task breakdown — the same content the host sees in-app. Available once session.notes_ready fires.
GET /v1/sessions/{session_id}/notes
200 OK
{
"session_id": "sess_9f2a…",
"ready": true,
"summary": "Kickoff for the Q3 launch…",
"decisions": ["Ship the beta on the 14th"],
"tasks": [
{ "text": "Draft the launch email", "owner": "Jane" }
]
}ready is false with empty fields until the call ends and notes are generated (also signalled by the session.notes_ready webhook).
Errors
Errors return the right HTTP status with a stable machine-readable code and a human message.
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}invalid_requestA field is missing or malformed.unauthorizedMissing or invalid API key.forbiddenKey lacks access to this resource.not_foundNo session with that id.rate_limitedToo many requests — back off and retry.server_errorSomething broke on our end. Safe to retry.Rate limits
The default limit is 120 requests/minute per key. Every response carries your current budget in headers — read them and back off on 429.
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1721840400Webhooks
Subscribe to lifecycle events to track usage or route tasks into your own system. Add endpoints in the dashboard; every delivery is signed so you can verify it came from us.
session.startedsession.participant_joinedsession.participant_leftsession.endedsession.recording_readysession.notes_readytask.assigned
{
"event": "session.notes_ready",
"session_id": "sess_9f2a…",
"metadata": { "reference_id": "abc123" },
"created_at": "2026-07-20T15:05:00Z"
}