Skip to main content
The cutover API turns a hard-cutover weekend from a manual SQL playbook into a sequenced run of HTTP calls. Every step writes to an append-only audit log so a future operator can reconstruct exactly what happened. This guide covers the production cutover flow. For the data-load that runs in the days leading up to cutover, see Migrating Data into Arcus and the bulk import + jobs cluster.

When to use this API

Use the cutover cluster when you have already finished:
  1. Migration loads complete via POST /v1/entities/{eid}/migration/{resource}/bulk for accounts, products, orders, journal-entries, vendor-bills, ap-payments and the other supported resources. Each call takes up to 1,000 records (some resources have a lower per-call limit, and a call over it returns 400 batch_too_large), needs an external_source naming the system the records came from, takes a conflict_mode of skip (the default), update, replace or error for records that already exist, and returns 202 with a job to poll. Send ?dry_run=true first to validate a batch without writing anything.
  2. Reconciliation checks pass. Trial balance ties; AR aging matches the source; AP aging matches the source; resource counts within tolerance. See Reconciliation API.
  3. Pilot slices verified. At minimum one named customer, then a wider slice, then a single fiscal year, then a full dry run into a clean entity. Each slice walked end to end in the Arcus UI.
Once those three gates are green the cutover sequence is a six-call API workflow.

Required scope

Cutover endpoints are gated on two elevated scopes: migration:admin is intentionally restricted. Grant it only to the operator who will execute the cutover window. Both migration:admin and migration:write must be present on the same key for the destructive endpoints. Issue the key from Settings > System > Developers (the API Keys tab), tick both elevated-scope checkboxes, acknowledge the elevated-permission warning, and store the secret in your password manager. Revoke the key immediately after cutover is complete.

The state machine

GET /v1/entities/{eid}/migration/cutover/status reports one of six current_state values:
The state is read from the latest action in the cutover log. At any point after freeze the operator can call POST .../rollback. Rollback emits operator commands; it does NOT auto-execute a snapshot restore.

Step 1: freeze the entity

reason is optional and is shown in the audit log; without it the reason is “Cutover initiated via API”. Response:
Capture the freeze_token. You need it for the snapshot and swap steps. After freeze, every write from a caller without the freeze token returns HTTP 423 Locked with error: "entity_frozen" and type: "cutover_error". Browser users continue to see read-only data. A caller whose key has the migration:write scope and who sends the token in an X-Freeze-Token header is let through, which is how the bulk migration calls keep working during the freeze, so a soft-cutover top-up sync is still possible. If you call freeze on an already-frozen entity, you get the existing freeze_token back with idempotent: true. The call is idempotent.

Step 2: take the database snapshot

freeze_token is required (a missing one returns 422 missing_freeze_token); description is an optional note stored in the cutover log. Response (202 Accepted, async):
The call returns as soon as the snapshot is requested, and estimated_completion_at is the estimate to plan around (about 15 minutes in the live environment). The status and log endpoints record that the snapshot was requested and keep the snapshot_id; they do not report when the snapshot finishes, so allow for the estimate (or confirm with your Arcus contact) before you swap. In the development environment the call returns a stub snapshot (dev_stub: true) and no real snapshot is taken. In the live environment the call is refused with 403 prod_gate_required until Arcus has enabled cutover snapshots for the cutover window. The snapshot is the rollback target. Do not proceed to swap until the snapshot should be complete. Snapshot retention is unlimited by default; sweep manually post-cutover-window if you no longer need it.

Step 3: emit swap commands

snapshot_id and freeze_token are both required (a missing one returns 422 missing_snapshot_id or 422 missing_freeze_token). You can also name the application version the commands should target; the API reference lists the optional field for it. Response:
instructions holds one operator command or note per swap target: the application alias, the DNS flip and the identity provider swap. The exact strings are environment-specific; for a non-live environment the DNS and identity provider entries say that nothing needs to change. The API does NOT execute these commands. It emits the exact strings to run in your operator shell so you keep a clean audit trail of what changed at the infrastructure layer, and it records the swap in the cutover log. Run them in order. Confirm each succeeds before moving to the next.

Step 4: verify

as_of_date is optional and defaults to today. Verify runs four reconciliation reads against the entity:
  • Trial balance: total debits and total credits, and whether they balance.
  • AR aging: the total receivable.
  • AP aging: the total payable.
  • Resource counts: the counts of migrated records by resource.
The call passes (passed: true) only when all four reads ran without error and the trial balance is in balance. The AR aging, AP aging and count figures are returned for you to compare with the source system; the API does not compare them for you. Response:
If a read fails, it appears in results.errors[] as { "check": "...", "error": "..." } and passed is false. A trial balance that is out of balance also gives passed: false. A failed verification cannot be used to unfreeze. Either rollback (Step 6) or fix the data and re-run verify. verify_log_id is valid for 4 hours. After that it expires and you must re-verify before unfreezing.

Step 5: unfreeze

verify_log_id is the only field. Unfreeze is refused with 422 missing_verify_log_id when it is left out, and with 422 verify_required when it does not name a passed verification of this entity from the last 4 hours. Response:
The entity is now live on Arcus. Writes resume. The unfreeze records a cutover_completed action, and the status returns to idle. Revoke the API key.

Step 6 (only if needed): rollback

Rollback emits the operator commands required to restore the pre-cutover snapshot and re-point traffic at the legacy ERP. It does not auto-execute anything destructive.
The confirm field MUST equal {entity_id}:rollback. The API rejects any other value with HTTP 422 and the code rollback_confirmation_required, and a missing snapshot_id with 422 missing_snapshot_id. This is the same pattern Stripe Connect uses for destructive operations. You can also name the application version to roll back to (the API reference lists the optional field); without it the commands roll back to the previous version. Response:
The plan also carries the command that rolls the application alias back. The warnings list is part of the plan and is worth reading before you run anything. It says that the restore creates a NEW database instance and does not delete the existing one, that DNS must be moved by hand once the restored instance is available, and that any transaction committed after the snapshot was taken is lost on rollback. Run the commands in order. Confirm each succeeds. Customer-facing communication is your responsibility; the API does not send anything.

Status and log

At any point during cutover you can read the current state:
Or page through the full action log:
Both endpoints require only migration:read (a key with migration:write or migration:admin also works). Status returns frozen, current_state, frozen_at, frozen_reason, snapshot_id, the latest_action and the ten most recent entries in recent_log. The log is newest first and append-only; each entry has an action (freeze_engaged, snapshot_taken, swap_initiated, verify_result, unfreeze_engaged, rollback_initiated or cutover_completed), a status (in_progress, succeeded, failed or reverted), performed_at, any snapshot id, the verification_result, error_text and notes. Page it with limit (up to 100, default 20) and starting_after, set to the id of the last entry you received; has_more says whether another page exists.

Webhook events

If you have a webhook endpoint subscribed to the cutover event family, the API emits the following during a cutover:
  • migration.snapshot_taken
  • migration.cutover_initiated (when the swap commands are issued)
  • migration.cutover_verified
  • migration.cutover_completed
  • migration.cutover_rolled_back (only on rollback path)
The events migration.freeze_engaged and migration.unfreeze_engaged are registered but not sent yet; freezing and unfreezing show up in the cutover log only. Signing follows the standard HMAC-SHA256 pattern. See Webhooks.

Operational notes

  • Run cutover during a planned freeze window. Customers and operators see read-only data for the duration. Targets are typically a Friday EOD freeze, Saturday snapshot + swap + verify, Sunday final tests, Monday go-live.
  • Have the rollback playbook open in another tab. If verify fails on Saturday evening, you want zero ambiguity about how to back out.
  • Snapshots are not free. Cloud database snapshots are billed by GB-month; keep the snapshot for 30 to 90 days as your safety net, then delete it.
  • Do not skip the verify step. Unfreeze is gated on a successful verify within the last 4 hours. There is no override.
  • Capture the freeze_token on Step 1. It is the only path through Steps 2 to 5. If you lose it, you must call freeze again (which returns the existing token; safe).
  • Keep the snapshot retention long enough. Once the snapshot expires, rollback is no longer possible from this snapshot. Take a manual snapshot before deletion if you want a longer safety window.
  • Migrating Data into Arcus - the bulk-load pass that populates the entity before cutover.
  • Reconciliation API - the read endpoints that prove the data is correct before you flip the switch.
  • Webhooks - subscribe to cutover events for observability.
  • Idempotency - Idempotency-Key header semantics across all endpoints.