Migration API
The Migration API is a read-only export of an entire channel. It exists to move a channel, or a whole installation, to another Broadcast server: it returns everything needed to rebuild the channel elsewhere, including the secrets the regular API never returns.
It lives under /api/migration/v1, separate from the regular /api/v1 API, and nothing in it writes data.
Warning
Treat a token used here like a database backup. The Migration API returns email server credentials (SMTP passwords, AWS secret keys, provider API keys) and channel API token values in plain text, and lists every user on the installation with their sudo flag. It accepts any channel ID. Use a dedicated admin API token for a migration, keep it out of logs and shared tools, and delete it when the migration is done.
Authentication
Every request needs an admin API token in the Authorization header. Channel tokens are rejected, because the export reaches across channels. Create admin API tokens under Application → Admin API Tokens, see API Authentication.
curl https://broadcast.example.com/api/migration/v1/manifest?broadcast_channel_id=1 \ -H "Authorization: Bearer YOUR_ADMIN_API_TOKEN"
Any valid, unexpired admin API token can read the Migration API; its resource permissions do not narrow it. The whole API is switched off in demo mode and answers 403.
Every endpoint except channels and users needs a broadcast_channel_id query parameter naming the channel to export. Start with channels to find the IDs.
| Status | Meaning |
|---|---|
400 Bad Request |
broadcast_channel_id is missing |
401 Unauthorized |
No token, an unknown token, a channel token, or an expired admin token |
403 Forbidden |
The installation is in demo mode |
404 Not Found |
The channel or file asset does not exist |
Manifest
GET /api/migration/v1/manifest?broadcast_channel_id=1
Sizes the export before you start: how many of each record the channel has, so you can check the import afterwards.
{ "export_format_version": 1, "exported_at": "2026-10-03T14:30:00Z", "broadcast_channel": { "id": 1, "name": "Newsletter", "slug": "newsletter" }, "counts": { "subscribers": 12840, "templates": 14, "segments": 6, "sequences": 3, "email_servers": 2, "opt_in_forms": 4, "broadcasts": 11, "webhook_endpoints": 1, "tokens": 2, "global_suppressions": 37, "unsubscribed_emails": 412, "tags": 18, "link_redirects": 230, "file_assets": 41 }, "recent_history": { "days": 90, "broadcasts": 11, "outbound_receipts": 98412 } }
broadcasts and recent_history cover only the last days_history days (see below). global_suppressions counts the installation-wide list plus anything recorded against this channel.
Note
unsubscribed_emails is the channel’s own suppression list, exported by /unsubscribed_emails (Broadcast 2.40.0 or later). global_suppressions is the installation-wide list, exported by /suppressions. A complete export needs both.
Resources
Each resource is a GET that returns { "data": [...] }.
| Endpoint | Returns | Paginated |
|---|---|---|
/channels |
Every channel on the installation | No |
/users |
Every user, with their sudo flag, system permissions and channel permissions | No |
/subscribers |
The channel’s subscribers, including custom data and tags | Yes |
/tags |
Subscriber tags used in the channel | No |
/subscriber_histories |
Subscriber history entries | Yes |
/suppressions |
Global suppressions, plus any recorded against this channel | Yes |
/unsubscribed_emails |
The channel’s own suppression list (2.40.0 or later) | Yes |
/templates |
Templates | Yes |
/segments |
Segments with their rule groups and rules | No |
/sequences |
Sequences with their steps | No |
/email_servers |
Email servers, including credentials | No |
/opt_in_forms |
Opt-in forms | No |
/webhook_endpoints |
Webhook endpoints | No |
/tokens |
Channel API tokens, including their values | No |
/broadcasts |
Broadcasts from the last days_history days |
Yes |
/outbound_receipts |
Delivery receipts for those broadcasts | Yes |
/link_redirects |
Tracked links | Yes |
/link_clicks |
Link clicks from the last days_history days |
Yes |
/file_assets |
File asset metadata | Yes |
All paths are relative to /api/migration/v1.
Pagination
Paginated endpoints take limit (1 to 250, default 250) and offset (default 0), and return a pagination object next to data:
{ "data": [ ... ], "pagination": { "total": 12840, "limit": 250, "offset": 0, "has_more": true } }
Keep requesting with offset increased by the limit in the response until has_more is false. Advance by the returned limit, not the one you asked for: a limit above 250 is reduced to 250.
Recent history
broadcasts, outbound_receipts, link_clicks and the manifest accept days_history (1 to 365, default 90). Older sends are left out so an export of a busy channel stays a manageable size.
The channel suppression list
GET /api/migration/v1/unsubscribed_emails?broadcast_channel_id=1
Returns every address on the channel’s own suppression list, paginated like the other resources. Its pagination.total matches counts.unsubscribed_emails in the manifest, so you can check that nothing was missed.
{ "data": [ { "id": 88, "email": "former.subscriber@example.com", "broadcast_channel_id": 1, "created_at": "2026-03-14T09:12:00Z", "updated_at": "2026-03-14T09:12:00Z" } ], "pagination": { "total": 412, "limit": 250, "offset": 0, "has_more": true } }
This is a different list from /suppressions, which holds the installation-wide global list. Export both. On the new server, add these addresses to the channel with the bulk endpoint of the Suppressions API.
Before 2.40.0 this endpoint did not exist. On an older server, read the channel list with GET /api/v1/suppressions instead.
Downloading file assets
GET /api/migration/v1/file_assets/:id/download?broadcast_channel_id=1
Returns the original file’s bytes, for re-uploading on the new server. A file asset whose file is missing answers 404.
Using an SDK
The official SDKs wrap the Migration API, handling pagination for you: client.migration.manifest and client.migration.each_record in the Ruby SDK, with the same methods in the Node, Python and PHP SDKs. See SDKs & Client Libraries.
| SDK | Channel suppression list |
|---|---|
| Ruby | client.migration.each_record(:unsubscribed_emails) |
| Node | client.migration.eachRecord('unsubscribedEmails') |
| Python | client.migration.each_record("unsubscribed_emails") |
| PHP | $client->migration->eachRecord('unsubscribedEmails') |
These collections need Ruby 0.6.0, Node 0.3.0, Python 0.3.0 or PHP 0.4.0 or later, and Broadcast 2.40.0 or later.