meetstream-migrate
One-command migration from Recall.ai to MeetStream.ai. Scans your codebase, maps every API call, shows the diff, and migrates — safely, with warnings for anything that needs a human look.
v2.1.2 — near feature parity with Recall.ai (verified live against production, July 2026). MeetStream matches Recall across recording, per-participant streams, real-time audio/video/ transcript, chat + image output, Google and Outlook calendar, scheduling, signed-in bots, and retention — plus native AI summaries and MIA voice agents that Recall doesn't have. The only gaps: pause/resume recording and live video output into the call (both shipping soon). See Feature parity & gaps below.
Quick Start
pip install meetstream-migrate
meetstream-migrate scan ./your-recall-project
Commands
# Scan (read-only) — finds every Recall.ai reference, writes NOTHING
meetstream-migrate scan ./your-project
# Dry run — shows exactly what would change, still writes nothing
meetstream-migrate migrate ./your-project --dry-run
# Migrate — applies all transformations (prompts before writing)
meetstream-migrate migrate ./your-project --yes
# Verify your MeetStream credentials + API reachability
meetstream-migrate test --api-key YOUR_MEETSTREAM_API_KEY
CLI reference
meetstream-migrate <command> [path] [options]
Commands:
scan [path] Scan only — show what would change
migrate [path] Scan & migrate a project
test Run API tests against MeetStream.ai
Options:
--yes, -y Skip the confirmation prompt
--dry-run Show changes without writing files
--api-key KEY MeetStream API key (for the test command)
--verbose, -v Detailed output
--help, -h Show help
What it does
1. Scan — recursively finds:
- Recall.ai base URLs (
us-east-1.recall.ai,eu-west-1.recall.ai,api.recall.ai) - API endpoint calls (
/api/v1/bot/,/api/v1/bot/{id}/audio/, etc.) - Recall-specific field names (
meeting_url,recording_mode,transcription_options,metadata, …) - Webhook handlers (
bot.status_change,joining_call,in_call_recording,call_ended,done,fatal, …) - SDK imports (
require('recall-ai'),import recall) - Environment variables (
RECALL_API_KEY,RECALL_API_URL,RECALL_WEBHOOK_SECRET)
Supported file types: .js .ts .jsx .tsx .mjs .cjs .py .go .rb .java .kt .rs .php .cs .swift .env .yaml .yml .json .toml .ini .cfg
2. Display — endpoint mapping table, field mapping table, webhook event mapping, and a compatibility check (auto-migratable vs. needs manual review).
3. Transform — base URL swap, endpoint path rewrites, field renames, webhook event renames, env var renames, import/require updates. Every change carries an inline warning when it needs a second look.
4. Report — what changed, what needs manual review, and a post-migration checklist.
Endpoint Mapping
| Recall.ai | MeetStream.ai | Notes |
|---|---|---|
POST /api/v1/bot/ |
POST /api/v1/bots/create_bot |
Field names differ (see below) |
GET /api/v1/bot/{id}/ |
GET /api/v1/bots/{id}/detail |
Also GET /api/v1/bots/{id}/status |
POST /api/v1/bot/{id}/leave_call/ |
GET /api/v1/bots/{id}/remove_bot |
⚠️ Method changes POST → GET |
DELETE /api/v1/bot/{id}/ |
DELETE /api/v1/bots/{id}/delete |
|
GET /api/v1/bot/ |
GET /api/v1/bots |
List all bots |
GET /bot/{id}/audio/ |
GET /bots/{id}/get_audio |
Presigned S3 URL, valid 1h |
GET /bot/{id}/video/ |
GET /bots/{id}/get_video |
Presigned S3 URL, valid 10min |
GET /bot/{id}/transcript/ |
GET /transcript/{transcript_id}/get_transcript |
⚠️ Uses transcript_id, not bot_id |
GET /bot/{id}/participants/ |
GET /bots/{id}/get_participants |
|
GET /bot/{id}/chat_messages/ |
GET /bots/{id}/get_chats |
|
POST /bot/{id}/send_chat_message/ |
POST /bots/{id}/send_message |
REST body { "message": "..." } — drop Recall's to/pin fields |
POST /bot/{id}/output_video/ |
POST /bots/{id}/send_image |
Public img_url (not base64) |
POST /bot/{id}/output_audio/ |
WebSocket sendaudio command |
⚠️ Manual — no REST equivalent, use socket_connection_url bridge |
POST /bot/{id}/output_media/ |
WebSocket bridge (partial) | ⚠️ Manual — covers audio + static image today |
POST /bot/{id}/pause_recording/ |
— | ⛔ Not yet supported (shipping soon) |
POST /bot/{id}/resume_recording/ |
— | ⛔ Not yet supported (shipping soon) |
GET /bot/{id}/screenshots/ |
GET /bots/{id}/get_screenshots |
|
GET /bot/{id}/speaker_timeline/ |
GET /bots/{id}/get_speaker_timeline |
|
POST /api/v1/webhook/ |
callback_url field on create_bot |
Per-bot, not account-level |
GET /bot/{id}/calendar/… |
POST /calendar/create_calendar (underscore) |
Body: google_client_id/google_client_secret/google_refresh_token |
Field Mapping
| Recall.ai | MeetStream.ai | Notes |
|---|---|---|
meeting_url |
meeting_link |
|
recording_mode (string) |
video_required (boolean) |
"speaker_view"/"gallery_view" → true, "audio_only" → false |
metadata |
custom_attributes |
Values must be strings |
transcription_options |
recording_config.transcript.provider.{provider} |
Object structure differs — see provider config |
noone_joined_timeout |
voice_inactivity_timeout |
|
destination_url |
webhook_url (nested in live_transcription_required) |
|
assembly_ai |
assemblyai |
Also: assemblyai uses speech_models[] + language_code, not model/language |
output_media.websocket.url |
socket_connection_url.websocket_url |
Webhook Events — live-verified against production
MeetStream's envelope key is event — the same key Recall uses. No envelope rename needed. Alongside it: bot_id, message, status_code (200 success / 500 failure), custom_attributes, and on lifecycle events, bot_status.
{ "event": "bot.inmeeting", "bot_id": "...", "bot_status": "InMeeting",
"message": "...", "status_code": 200, "custom_attributes": {} }
Two-layer model: bot.stopped fires once; bot_status tells you why — Stopped (normal) / NotAllowed (lobby timeout) / Denied (host denied) / Error (crash). Recall's separate end-reason events collapse into this single event + status.
| Recall.ai Event | MeetStream event |
bot_status |
status_code |
|---|---|---|---|
bot.joining_call |
bot.joining (may fire up to 3×) |
Joining |
200 |
bot.in_waiting_room |
bot.in_waiting_room |
InWaitingRoom |
200 |
bot.in_call_not_recording |
bot.inmeeting |
InMeeting |
200 |
bot.in_call_recording |
bot.recording |
Recording |
200 |
| (none — new) | bot.leaving |
Leaving |
200 |
| (none — new) | bot.error (non-terminal streaming-provider error; bot keeps running) |
InMeeting |
— (omitted) |
bot.call_ended |
bot.stopped |
Stopped |
200 |
bot.call_ended (lobby timeout) |
bot.stopped |
NotAllowed |
500 |
bot.call_ended (host denied) |
bot.stopped |
Denied |
500 |
bot.fatal |
bot.stopped |
Error |
500 |
recording.done |
manifest.completed → audio.processed → video.processed (if video_required) |
— | 200 |
transcript.done |
transcription.processed |
— | 200 |
transcript.failed |
transcription.failed |
— | 500 |
| (post-call) | bot.done — post-call providers only |
Done |
200 / 500 |
| (retention) | data_deletion |
— | 200 |
Streaming-only providers (deepgram_streaming, assemblyai_streaming, meeting_captions) do not fire transcription.processed/transcription.failed/bot.done — their terminal event is audio.processed. Don't wait on bot.done for a streaming bot, or your handler hangs forever.
transcript_id is never in the webhook payload. Resolve it via GET /bots/{id}/detail (bot_details.transcript_id), the create_bot response, or GET /bots/{id}/transcriptions.
Full bot_status enum: Joining, InWaitingRoom, InMeeting, Recording, Leaving, Stopped, NotAllowed, Denied, Error, Done.
Real-time events
| Recall.ai | MeetStream | Notes |
|---|---|---|
transcript.data / transcript.partial_data |
delivered to live_transcription_required.webhook_url |
Chunk shape: { speakerName, timestamp, transcript, words[] } |
participant_events.join / .leave |
same event names, requires recording_config.realtime_endpoints |
Different payload shape — nested under data.bot.{id,metadata}, no top-level bot_id |
Breaking changes to review manually
- Transcript retrieval uses
transcript_id(from thecreate_botresponse,/detail, or/transcriptions), notbot_id. recording_mode(Recall string) →video_required(MeetStream boolean).- Chat messages: Recall's separate
POST /send_chat_message/→ MeetStream'sPOST /bots/{id}/send_message. - Webhook registration: Recall registers globally via
POST /webhook/; MeetStream usescallback_urlper bot at creation. - Webhook event values rename (see table above) — but the envelope key stays
event, so most handler code needs a value rename, not a restructure. - Post-call processing arrives as discrete events (
manifest.completed,audio.processed,transcription.processed/transcription.failed,video.processed,bot.done) instead of onebot.done-then-fetch signal. - Live transcription:
real_time_transcription.destination_url→live_transcription_required.webhook_urloncreate_bot. - Pause/resume recording has no MeetStream equivalent yet — remove these calls, or segment via
remove_bot+ re-create. - Output audio/media into the call is REST on Recall, WebSocket-bridge on MeetStream — needs a real refactor, not a rename (flagged, not auto-migrated).
Post-Migration Checklist
- Set
MEETSTREAM_API_KEY(get one at app.meetstream.ai/api-keys) - Review every ⚠️ warning from the migration output
- Update webhook handlers: keep branching on
event, rename the values, and checkbot_statusforbot.stopped's reason - Store
transcript_idfor transcript retrieval — fromcreate_bot,/detail, or/transcriptions - Add handlers for
manifest.completed,audio.processed,transcription.processed/failed,video.processed - Verify the
recording_mode→video_requiredboolean logic - Remove any
pause_recording/resume_recordingcalls (not supported yet) - Run
meetstream-migrate test --api-key YOUR_KEYto confirm connectivity
Feature parity & gaps
| Recall capability | MeetStream | Migrator behaviour |
|---|---|---|
| Create/list/get/remove/delete bot | ✅ | auto-mapped |
| Recording (audio/video) + retention | ✅ | auto-mapped |
| Per-participant streams | ✅ | get_audio_streams / get_recording_streams |
| Real-time audio/video/transcript | ✅ | live_audio_required / live_video_required / live_transcription_required |
| Send chat message | ✅ | send_chat_message → REST send_message |
| Output image/camera frame | ✅ | output_video → send_image (public URL) |
| Calendar (Google + Outlook) + scheduling | ✅ | calendar/events/{id}/bot → calendar/schedule/{id} |
| Signed-in / OAuth bots | ✅ | Google login domains/logins; Zoom OBF |
| AI summaries | ✅ (MeetStream-only) | GET /bots/{id}/summary |
| Conversational voice agents (MIA) | ✅ (MeetStream-only) | agent_config_id on create_bot |
| Output audio / interactive media | ⚠️ partial | WebSocket bridge (manual, flagged) |
| Pause/resume recording | ⛔ not yet | flagged — shipping soon |
| Live video output into the call | ⛔ not yet | send_image covers static frames only |
Run scan first to see exactly which of these appear in your codebase before migrating.
Requirements
Python ≥ 3.8
Development
pip install -e .
python -m pytest test/ # or: python -m meetstream_migrate.cli test --api-key YOUR_KEY
License
MIT © MeetStream.ai — meetstream.ai · docs.meetstream.ai
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_migrate-2.3.0.tar.gz.
File metadata
- Download URL: meetstream_migrate-2.3.0.tar.gz
- Upload date:
- Size: 22.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6c370609389fa35ed9818e59483cafd5c2d3d364bfb75f149bdb1b161334a97
|
|
| MD5 |
dda28dc37036fc43775e9ed964d6eb7e
|
|
| BLAKE2b-256 |
dc259fae6c6ac3d861f8fc8fdd2cfccfc83705a029f86ea95d909eeab0daabae
|
File details
Details for the file meetstream_migrate-2.3.0-py3-none-any.whl.
File metadata
- Download URL: meetstream_migrate-2.3.0-py3-none-any.whl
- Upload date:
- Size: 19.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
47e29ef0c111fc906bf4fd8e962c26d2864d8e6ad38f99132b3d8a595cea91b6
|
|
| MD5 |
c972c6a0d2c1e38849495cd780d063a7
|
|
| BLAKE2b-256 |
6b36732e6c2faf411d5ddba3cf4e9119cf38b049228e23a96d8753192c41ac5e
|