MeetStream SDK for Python
Send AI bots into Zoom, Google Meet and Microsoft Teams. Record, transcribe, summarize and stream meetings, and run MIA voice agents that talk back.
pip install meetstream-sdk
Python 3.8+. Sync and async. Ships type information.
Quickstart
from meetstream import MeetStream
meetstream = MeetStream() # reads MEETSTREAM_API_KEY
bot = meetstream.bots.create({
"meeting_link": "https://meet.google.com/abc-defg-hij",
"bot_name": "Notetaker",
"recording_config": {
"transcript": {"provider": {"deepgram": {"model": "nova-3", "language": "en"}}}
},
})
# Bounded wait - never spins forever, even on a streaming-only bot.
transcript = meetstream.transcripts.wait_for(bot["transcript_id"])
Get a key at app.meetstream.ai/api-key.
Async
from meetstream import AsyncMeetStream
async with AsyncMeetStream() as meetstream:
bot = await meetstream.bots.create({"meeting_link": "https://zoom.us/j/123"})
status = await meetstream.bots.status(bot["bot_id"])
Why this SDK
The MeetStream API has a handful of behaviours that look like bugs until you know them. This SDK encodes all of them so you do not rediscover them in production.
| The trap | What the SDK does |
|---|---|
202 sits inside the 2xx range and means "not ready yet", not success |
Raises NotReadyError instead of handing you an empty body |
507 looks like a failure but is an idempotent replay |
Returns the original resource as success |
Streaming-only providers return 202 forever - no post-call transcript ever exists |
wait_for() is always bounded and tells you exactly why it gave up |
Transcripts are keyed by transcript_id, not bot_id |
transcripts.get() takes the right id |
Segments use transcript, not text |
Documented at every call site |
remove_bot is a GET |
bots.remove() handles it |
REST uses Token, the MCP server uses Bearer |
Correct scheme sent automatically |
MIA takes only agent_config_id |
Extra bridge fields are the usual cause of a silent agent |
What you can do
Bots - lifecycle, media, live interaction
meetstream.bots.create(params, idempotency_key=uuid)
meetstream.bots.list()
meetstream.bots.status(bot_id)
meetstream.bots.detail(bot_id) # includes transcript_id
meetstream.bots.summary(bot_id) # AI summary
meetstream.bots.remove(bot_id) # leave, keep the data
meetstream.bots.delete_data(bot_id) # irreversible
meetstream.bots.audio(bot_id)
meetstream.bots.video(bot_id)
meetstream.bots.audio_streams(bot_id) # per-participant audio
meetstream.bots.recording_streams(bot_id) # per-participant video
meetstream.bots.screenshots(bot_id)
meetstream.bots.wait_for_audio(bot_id) # bounded
meetstream.bots.participants(bot_id)
meetstream.bots.chats(bot_id)
meetstream.bots.speaker_timeline(bot_id)
meetstream.bots.send_message(bot_id, "Recording has started.")
meetstream.bots.send_image(bot_id, "https://…/slide.png", display_duration=5)
meetstream.bots.pause_recording(bot_id) # privacy window
meetstream.bots.resume_recording(bot_id)
Transcripts
meetstream.transcripts.get(transcript_id)
meetstream.transcripts.wait_for(transcript_id, timeout=900)
meetstream.transcripts.list_for_bot(bot_id)
meetstream.transcripts.transcribe_bot_audio(bot_id) # rescue a streaming-only bot
Calendar - auto-join and scheduling
meetstream.calendar.connect_google({
"google_client_id": ..., "google_client_secret": ..., "google_refresh_token": ...,
})
meetstream.calendar.connect_outlook({...})
meetstream.calendar.events()
meetstream.calendar.schedule_event(event_id)
meetstream.calendar.list_scheduled_bots()
meetstream.calendar.reschedule_bot(bot_id, "2026-09-01T10:00:00Z")
meetstream.calendar.enable_auto_schedule()
MIA voice agents
agent = meetstream.mia.create({
"agent_name": "Meeting Assistant",
"mode": "pipeline", # or "realtime" for speech-to-speech
"model": {"provider": "openai", "model": "gpt-4.1",
"first_message": "Hi, I'm an AI assistant on this call."},
"voice": {"provider": "openai", "voice_id": "nova"},
"transcriber": {"provider": "deepgram", "model": "nova-3",
"boostwords": ["Acme", "MeetStream"]},
"agent": {"tools": [], "mcp_servers": [], "enable_interruptions": True},
"wake_word": {"enabled": True, "words": ["hey acme"], "timeout": 30},
})
# Attach it with ONE field. Adding socket_connection_url or
# live_audio_required alongside is the usual cause of a silent agent.
meetstream.bots.create({
"meeting_link": meeting_link,
"agent_config_id": agent["agent_config_id"],
})
boostwords fixes most "it mishears our company name" complaints, and mcp_servers is what turns a talking bot into one that can do work mid-call.
Integrations - Google signed-in bots, Zoom OAuth, your own S3
meetstream.google_logins.create_domain({...})
meetstream.google_logins.create({...})
meetstream.zoom.authorize_url()
meetstream.zoom.list_connections()
meetstream.storage.set({"provider": "aws", "bucket_name": ..., "region": ...})
Webhooks
Verify before you trust. Pass the raw body - re-serializing a parsed dict changes key order and the signature will never match.
from flask import Flask, request
from meetstream import parse_webhook, is_terminal, describe_stop
app = Flask(__name__)
@app.post("/webhook")
def webhook():
try:
event = parse_webhook(
request.get_data(), # raw bytes, not request.json
request.headers.get("X-MeetStream-Signature", ""),
os.environ["WEBHOOK_SECRET"],
)
except ValueError:
return "", 401
if is_terminal(event):
print(describe_stop(event))
return "", 200 # ack fast, process async
bot.stopped is the single terminal event and always carries status_code: 200 - the reason lives in bot_status (Stopped, NotAllowed, Denied, Error). bot.error is not terminal; the bot keeps running. Streaming-only providers stop at audio.processed and never emit bot.done.
Errors
from meetstream import NotReadyError, RateLimitError, BadRequestError, MeetStreamError
try:
meetstream.transcripts.get(transcript_id)
except NotReadyError:
... # 202 - poll again
except RateLimitError as e:
time.sleep(e.retry_after or 5)
except BadRequestError as e:
print(e.message) # the API's own message
except MeetStreamError as e:
print(e.status, e.request_id)
AuthenticationError (401) means the header never arrived. PermissionError (403) means the key itself was rejected. That distinction saves a lot of debugging.
Configuration
meetstream = MeetStream(
api_key=os.environ["MEETSTREAM_API_KEY"],
base_url="https://api.meetstream.ai/api/v1",
timeout=60.0,
max_retries=2, # 429 and 5xx, exponential backoff, honours Retry-After
default_headers={},
http_client=my_httpx_client,
)
| Variable | Purpose |
|---|---|
MEETSTREAM_API_KEY |
Your key. Required unless passed explicitly |
MEETSTREAM_API_URL |
Override the API base |
Anything not yet wrapped is reachable through the raw transport:
meetstream.http.get("/some/new/endpoint")
Typed
The package ships py.typed, so mypy and pyright pick up its annotations with no stub package.
Also available
| What | Where | |
|---|---|---|
| 🔌 | MCP server - 19 tools for any MCP client | @meetstream/mcp |
| 📦 | TypeScript SDK | @meetstream/sdk |
| ⌨️ | CLI | @meetstream/cli |
| 🧪 | Labs - runnable templates | labs |
Links
Documentation · API reference · Errors · Webhooks · MIA · support@meetstream.ai
MIT licensed.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file meetstream_sdk-1.0.0.tar.gz.
File metadata
- Download URL: meetstream_sdk-1.0.0.tar.gz
- Upload date:
- Size: 20.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ddb70f2a17162ddb30499b1ef1ac5ab195da08213101dedc778ad8f18a8bfa6
|
|
| MD5 |
2abb43939f28964d21b44632141a6cf8
|
|
| BLAKE2b-256 |
193e08083d00bbd1c60af3a2603ab19bf93738bfac4ca60a08f75ec1f5572bab
|
File details
Details for the file meetstream_sdk-1.0.0-py3-none-any.whl.
File metadata
- Download URL: meetstream_sdk-1.0.0-py3-none-any.whl
- Upload date:
- Size: 19.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3dd9bd0912ae104b9df91c62dcf144d6274751eed3b4058324fcb73e17dea122
|
|
| MD5 |
add3dd36d2e5017720f5a5ef01d49832
|
|
| BLAKE2b-256 |
11ea1412f17692f67e1e1888a1e920f1029d01423f46f6126e419b5ea695c2cb
|