Meeting Room API
The Meeting Room API lets your application start and stop live meeting transcriptions via simple REST calls. When a meeting is active, Sasha sends real-time events to your callback URL — transcript segments, participant changes, coaching insights, and status updates.
How It Works
Your application makes REST calls to control meeting transcription. Sasha joins the meeting as a bot participant, transcribes the conversation, and delivers events back to your application via HTTP callbacks.
Prerequisites
- A Sasha Studio account with an active deployment
- An API key — create one in My Account > API Tokens
- A callback URL reachable from your Sasha instance (use ngrok to expose your local client to the remote Sasha server)
Quick Start
1. Start a meeting transcription
curl -X POST https://your-sasha.example.com/api/v1/meetings/start \
-H "X-API-Key: sk_your_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://teams.live.com/meet/abc123",
"title": "Daily Standup",
"callbackUrl": "https://your-app.com/events",
"project": "acme-corp"
}'
2. Receive events at your callback URL
Sasha POSTs events as they happen — transcript segments, participant joins/leaves, status changes, and coaching insights. Each event is signed with HMAC-SHA256 so you can verify authenticity.
3. Stop the transcription
curl -X POST https://your-sasha.example.com/api/v1/meetings/stop \
-H "X-API-Key: sk_your_key" \
-H "Content-Type: application/json" \
-d '{ "meetingId": "meeting_1707234567890_abc123def" }'
API Reference
All endpoints require authentication via the X-API-Key header.
Join a Meeting
Join and transcribe a live meeting. Sasha joins as a bot participant.
POST /api/v1/meetings/start
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
url |
string | Yes | Meeting URL (Teams or Google Meet) |
title |
string | No | Label for this meeting |
callbackUrl |
string | Yes | URL where Sasha sends real-time events |
project |
string | No | Sasha project to save the transcript into (see Projects API). Defaults to api |
saveTranscript |
boolean | No | Save a transcript document in the project. Defaults to true |
Response (201):
{
"meetingId": "meeting_1707234567890_abc123def",
"status": "joining",
"platform": "teams",
"project": "acme-corp",
"documentPath": "deployed-md-files/docs/acme-corp/meetings/February 11th - Daily Standup.md"
}
Stop a Meeting
Stop transcribing and leave the meeting.
POST /api/v1/meetings/stop
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
meetingId |
string | Yes | The meeting ID returned from the start call |
Response (200):
{
"meetingId": "meeting_1707234567890_abc123def",
"status": "stopping"
}
List Active Meetings
Returns all meetings currently being transcribed.
GET /api/v1/meetings/status
Response (200):
{
"meetings": [
{
"meetingId": "meeting_1707234567890_abc123def",
"status": "live",
"platform": "teams",
"title": "Daily Standup",
"startedAt": "2026-02-10T14:00:00Z",
"participantCount": 5,
"segmentCount": 42,
"project": "acme-corp",
"documentPath": "deployed-md-files/docs/acme-corp/meetings/February 10th - Daily Standup.md"
}
]
}
Get Transcript
Retrieve the full transcript for a meeting (live or completed).
GET /api/v1/meetings/{meetingId}/transcript
Response (200):
{
"meetingId": "meeting_1707234567890_abc123def",
"status": "live",
"project": "acme-corp",
"documentPath": "deployed-md-files/docs/acme-corp/meetings/February 10th - Daily Standup.md",
"segments": [
{
"speaker": "Alice",
"text": "So about the roadmap for Q2...",
"timestamp": "2026-02-10T14:29:58.123Z"
}
]
}
Download Transcript Document
Download the saved markdown transcript file for a meeting.
GET /api/v1/meetings/{meetingId}/document
Response (200):
Returns the markdown file with Content-Type: text/markdown and a Content-Disposition header for download. Only available when saveTranscript was true (the default) when joining the meeting.
Response (404):
{
"error": "No saved document for this meeting"
}
Callback Events
When you provide a callbackUrl, Sasha POSTs events as they happen during the meeting. This is the core of the integration — your application receives live data without polling.
Event Format
Every callback is an HTTP POST with a JSON body:
{
"type": "segment_finalized",
"meetingId": "meeting_17234...",
"sequence": 42,
"timestamp": "2026-02-10T14:30:00.000Z",
"payload": {
"speaker": "Alice",
"text": "So about the roadmap for Q2...",
"timestamp": "2026-02-10T14:29:58.123Z"
}
}
Callback Headers
| Header | Description |
|---|---|
X-Sasha-Signature |
sha256=<HMAC-SHA256> for verification |
X-Sasha-Event |
Event type (e.g., segment_finalized) |
X-Sasha-Meeting-Id |
Meeting ID |
X-Sasha-Delivery-Id |
Unique ID per delivery attempt |
Event Types
| Type | Description |
|---|---|
meeting_status |
Status changes: joining, lobby, live, leaving, ended, error |
caption_partial |
Live evolving caption (high frequency, useful for real-time display) |
segment_finalized |
Clean finalized transcript segment (use this for persistence) |
coach_insight |
AI coaching insight with category |
participant_joined |
Someone joined the meeting |
participant_left |
Someone left the meeting |
document_saved |
Transcript document was flushed to disk (includes documentPath, segmentsWritten, totalSegments) |
HMAC Signature Verification
Every callback includes an X-Sasha-Signature header. Verify it to confirm the event came from Sasha and hasn't been tampered with.
Node.js
import crypto from 'crypto';
function verifySignature(body, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// In your Express handler:
app.post('/events', express.raw({ type: 'application/json' }), (req, res) => {
const rawBody = req.body.toString('utf8');
const sig = req.headers['x-sasha-signature'];
if (!verifySignature(rawBody, sig, process.env.SIGNING_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(rawBody);
// Process event...
res.json({ received: true });
});
Python
import hmac
import hashlib
def verify_signature(body: bytes, signature: str, secret: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
Reference Client
The official reference client is a working demo that shows the full integration flow — API calls, callback handling, HMAC verification, and a live event feed in the browser.
Repository: github.com/context-is-everything/sasha-meeting-api-client
git clone https://github.com/context-is-everything/sasha-meeting-api-client.git
cd sasha-meeting-api-client
npm install
node index.js
The reference client includes setup guides for ngrok (needed so the remote Sasha server can reach your local client), database schema suggestions for persisting transcripts, and example code for building on top of the API.
Project Integration
Transcripts are saved into Sasha projects — the same directories visible in the Knowledge panel. Use the project field when joining a meeting to control where the transcript is stored.
- Default: Transcripts go into the
apiproject - Custom project: Set
"project": "acme-corp"to save into a specific project - No transcript: Set
"saveTranscript": falseto skip file creation (events still stream normally) - Browse saved transcripts: Use the Projects API to list projects and their meeting history
Troubleshooting
| Problem | Solution |
|---|---|
401 Invalid API key |
Check the key hasn't been revoked in My Account > API Tokens |
HMAC FAIL on events |
Ensure your signing secret matches the one shown when creating the API token |
| No events arriving | Check that your callback URL is reachable from the remote Sasha server. Use ngrok to expose your local client |
ECONNREFUSED |
Verify your Sasha URL is correct and the instance is running |
Events show no secret |
Not an error — set your signing secret to enable HMAC verification |