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.
Metadata
Release files for meetstream-sdk 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| meetstream_sdk-1.0.0.tar.gz | 20.3 kB | Without provenance | Details for meetstream_sdk-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| meetstream_sdk-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for meetstream_sdk-1.0.0-py3-none-any.whl |
Total release size: 40.2 kB
Release files / meetstream_sdk-1.0.0.tar.gz
| Download URL | meetstream_sdk-1.0.0.tar.gz |
|---|---|
| Size | 20.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6ddb70f2a17162ddb30499b1ef1ac5ab195da08213101dedc778ad8f18a8bfa6
|
|
BLAKE2b-256 checksum How to use checksums |
193e08083d00bbd1c60af3a2603ab19bf93738bfac4ca60a08f75ec1f5572bab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.14
|
Release files / meetstream_sdk-1.0.0-py3-none-any.whl
| Download URL | meetstream_sdk-1.0.0-py3-none-any.whl |
|---|---|
| Size | 19.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3dd9bd0912ae104b9df91c62dcf144d6274751eed3b4058324fcb73e17dea122
|
|
BLAKE2b-256 checksum How to use checksums |
11ea1412f17692f67e1e1888a1e920f1029d01423f46f6126e419b5ea695c2cb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.14
|
Security for meetstream-sdk 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.