Move an Expo Push (Classic) app to the current push service. Installed apps keep working and nobody has to update.
Before you start
You must be the owner or an admin of the account. A Classic app has a Migrate page in Native Notify, under Setup & docs. The installed app keeps calling the Classic SDK; Native Notify copies each registration to the current service.
What the page shows
Migrate lists five steps: Check your app, Copy devices, Credentials and a test, Your sends and Finish. Each one shows Done (a green check), Running… (a spinner), Your turn or Not yet, and a line at the top says what is happening and what to press next. The page updates by itself, so a copy you start with the API or an agent shows up without a reload.
1. Copy the devices in Native Notify
Open your app, then Setup & docs → Migrate and press Start copying. While the copy runs the step shows Running… with a progress bar, usually for under a minute, and there is nothing to press; when it finishes the step gets a green check and what was copied. Copying sends no push and changes nothing for your users. Devices keep their last-open dates, so the lapsed, active and new groups stay accurate, and Classic topic groups become groups. Every later registration from the installed app is copied too. Stop copying backs out until you finish.
2. Add push credentials and send a test
Each platform with devices that opened the app in the last 90 days needs working push credentials. In Migrate → Credentials and a test, pick your own phone from the devices that just opened the app, then add the Apple key or the Firebase service account. Native Notify sends one real push to that phone with the new credentials and saves them only if Apple or Google accepts it. Credentials that are already saved need a test push instead.
Saving a key changes how a Classic app sends
Once a Classic app has push credentials, its single and mass sends go straight to Apple and Google instead of through Expo, and group, topic and follow pushes also keep the Expo copy for a device that hasn't reported both its tokens yet, so that device can get two notifications until it next opens the app. Migrate tests a key before it saves it, so a wrong one can't break the sends that work today.
3. Finish
Press Finish migration. Native Notify checks every requirement again, then the app becomes a Current app and leaves the Classic view. Installed copies keep registering and keep getting pushes. Nothing in your Classic data is deleted.
From then on every send goes straight to Apple and Google, group, topic and follow pushes included, with sound and high priority. Expo is used only for a phone that has no Apple or Google token, or while the saved credentials fail their check.
With the API
Every call takes a Native Notify dashboard session or an MCP token. An app token is refused with 403.
| Request | What it does |
|---|---|
GET /api/apps/:appId/migrate | The picture: state (classic, bridging, ready, migrated, current), stateText (the state in plain words), steps (each with n, title and a phase: done, running, your_turn, waiting or blocked), blockers, warnings, credentials, tests and canFinish. ?check=1 also runs the live credential checks. |
POST /api/apps/:appId/migrate/start | Copy the devices and mirror every later registration. Optional inboxWriteThrough (boolean) also writes the Classic inbox the installed app reads. |
GET /api/apps/:appId/migrate/devices?minutes=15 | Copied devices that opened the app lately, with the last six characters of each token. |
POST /api/apps/:appId/migrate/credentials | { platform, deviceId, … } with the credential fields from Push credentials. Tests, then saves. A rejected test answers 200 with ok: false and saved: false. |
POST /api/apps/:appId/migrate/inbox | { enabled } turns the Classic inbox write-through on or off. |
POST /api/apps/:appId/migrate/finish | { confirm: true, acknowledge? } where acknowledge lists the blocker codes you accept. |
POST /api/apps/:appId/migrate/cancel | Stop copying before you finish. |
A test push to a copied device goes through Verify delivery with its deviceId. A migrating app never picks a device for you.
Blockers
Finish answers 409 not_ready with the blockers that stand in the way.
| Code | Clears when |
|---|---|
bridge_not_started, bridge_copying, bridge_failed, bridge_stale | The copy has run and refreshed. |
credentials_missing_ios, credentials_missing_android | You save credentials for that platform. |
credentials_invalid_ios, credentials_invalid_android | The saved key passes Apple's or Google's live check. |
test_needed_ios, test_needed_android | A test push to a copied device is accepted, or you pass its code in acknowledge (credentials that were already saved only). |
topics_not_imported | The copy has imported the topic groups. |
trial_expired | The account has an active plan. |
multi_subscriber | You pass its code in acknowledge. |
multi_subscriber_blocked | It doesn't clear. The app stays Classic. |
admin_only | An owner or admin does it. |
Not carried over
- Several ids on one device. A device on the current service belongs to one subscriber id, the one it registered under last. A few devices under two ids can be acknowledged. An app that reaches many devices through one id, as a group, can't move; it stays Classic. Use groups instead.
- Follow masters (a user following a user) aren't copied. Follow pushes keep working through the Classic API.
- Scheduled Classic pushes still send. Recreate them under Push notifications → Scheduling to keep managing them.
Errors
| Status | Code | Meaning |
|---|---|---|
404 | migration_unavailable | Migrate isn't open to this account yet. |
404 | app_not_found | The app isn't on this account or team. |
403 | admin_only | A developer-role teammate can read but not change. |
409 | not_classic | The app already runs on the current service. |
409 | not_ready | Finish found blockers. |
409 | not_started, nothing_to_cancel | Start the copy first; nothing to stop. |