SOP: Batch Agency Migrations
Use/admin/migrations when several sub-accounts need the same source-agency to MFM migration flow. Do not start batch transfers from the Admin Locations tab. The Locations migration card is for one-off visibility and recovery.
This workflow is live operations tooling. It calls CRM agency-transfer and SaaS V2 relink endpoints after an admin confirms the batch.
Quick links
- Batch board:
/admin/migrations - Single-location tracker:
/admin/locations, then the location detail card - Source UI:
app/admin/migrations/page.tsx - API route:
app/api/admin/migrations/batch/route.ts - Convex runner:
convex/admin/agencyMigrationBatch.ts
What the batch board does
The workflow has three parts.1
Pre-flight validation
Search for sub-accounts or paste location IDs in Advanced. The API validates
at most 200 candidates per request. It confirms the app is installed, reads
the source subscription status, resolves the source plan price, and
classifies each row before anything runs.
2
Queue live work
Click Migrate selected only after reviewing the runnable rows. The
request enqueues a Convex runner chain. The HTTP request does not run the
migration synchronously.
3
Monitor the status board
The board polls recent
agencyMigrations rows. Each row persists its step
state, last error, relink pause state, trial override audit fields, and
timestamps.Candidate classes
Held rows remain visible with reasons and are excluded from selection. The enqueue route re-validates the submitted rows server-side, so a stale browser state cannot force a held row into the runner.
Plan mapping
The relink must mirror the customer’s existing tier.
V2 SaaS plan amounts are whole dollars. Odd monthly prices, missing amounts, and stale price IDs hold for manual review.
Manual plan confirm accepts only
$1,300 or $1,500, plus an explicit trial end more than 24 hours in the future. It does not bypass the active-account gate.
Migration steps
Each migration persists these steps in order:unpausereactivates the source sub-account if needed.cancel_addoncancels the Ad Publishing add-on when one exists. If no add-on subscription is found, the step records a skip and continues.create_transferrequests the agency transfer from the source agency to MFM.accept_transferaccepts the transfer into MFM. The batch runner can run this step automatically. The single-location card also has Mark accepted for the manual CRM UI fallback.relink_v2enables SaaS V2 on the MFM side with the resolved target plan and price.
Status glossary
propagating is controlled by DEFAULT_TRANSFER_PROPAGATION_DELAY_MS in code, with an optional MFM_TRANSFER_PROPAGATION_DELAY_MS Convex env override. A 0 value makes relink immediate.
Relink pause and recovery
Use Pause relink when the transfer is accepted but the account should not relink yet. Pausing blocks:- the scheduled relink job,
- the stuck-propagation reconciler,
- manual Relink now actions.
relink_v2 immediately.
Trial handling after relink
The migration does not charge canceled customers during setup-pending. When the customer later adds a payment method, Stripe trial adjustment uses one of these paths:- monthly source plans use the normal win-back floor,
- annual source plans preserve the prepaid annual term when the source dates are available,
- manual overrides use the admin-confirmed trial end,
- missing annual anchors hold for manual review rather than charging on the 1-day floor.
accepted or relinked before applying trial changes.
Common problems
A row says “App not installed”
The batch route checksghlInstallations before classifying the row. Reinstall or reauthorize the marketplace app for that sub-account, then validate again.
A row is held for price mismatch
Confirm the current source plan with the customer record and the CRM subscription. If it should migrate, choose one of the two approved MFM V2 plans and an explicit trial end through the manual override prompt.A row failed during a live step
Read the row error and the step request/response preview. Fix credentials, source subscription state, or plan data first. Then retry the failed step from the single-location migration card or requeue the same location in the batch board. Failed rows do not auto-loop.A row stays in propagating
Check whether relink is paused. If it is paused, resume only after the transfer is safe to relink. If it is not paused and the wait has elapsed, the reconciler should retry. Use Relink now only after confirming the sub-account is available under MFM.
Source of truth
When behavior is unclear, check these files first:convex/admin/agencyMigrationPlan.tsfor classification, plan mapping, request builders, and propagation delay.convex/admin/agencyMigrationBatch.tsfor enqueue, runner concurrency, manual override, and board reads.convex/admin/agencyMigrations.tsfor status derivation, pause/resume, retry guards, and trial matching.convex/admin/agencyMigrationActions.tsfor live step execution and scheduled relink behavior.app/api/admin/migrations/batch/route.tsfor request validation and defense-in-depth checks.components/admin/AgencyMigrationBatchBoard.tsxfor admin-facing labels and actions.components/admin/AgencyMigrationCard.tsxfor single-location retry, manual acceptance, and relink recovery actions.

