<!-- Source: https://dev.dbmigratepro.com/docs/migrations-api -->

# Migrations API

Create and track migrations over REST. All endpoints require a bearer token
(see [Authentication](/docs/authentication)) and `Accept: application/json`.

## Migrate via API only (end-to-end)

No dashboard needed — you can run a full migration with just the API. The whole flow is:
**pre-flight → create → (pay, if PAYG) → poll → done.** Here's a complete, copy-paste script:

```bash
#!/usr/bin/env bash
set -euo pipefail

BASE=https://dev.dbmigratepro.com
TOKEN="YOUR_TOKEN"            # Settings → API Tokens (see Authentication)
SRC="mysql://user:pass@source-host:3306/app"
DST="postgresql://user:pass@target-host:5432/app"
auth=(-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json")

# 1. Pre-flight: confirm both databases are reachable, measure the source, preview the price.
curl -s -X POST "$BASE/api/migrations/preflight" "${auth[@]}" \
  -d "{\"source_url\":\"$SRC\",\"target_url\":\"$DST\"}" | jq '{ready, estimate, free_eligible}'

# 2. Create the migration.
resp=$(curl -s -X POST "$BASE/api/migrations" "${auth[@]}" \
  -d "{\"source_url\":\"$SRC\",\"target_url\":\"$DST\"}")
id=$(echo "$resp" | jq -r '.migration.id')
checkout=$(echo "$resp" | jq -r '.checkout_url // empty')

# 3. Pay if required. PAYG returns a checkout_url that a human authorizes in a browser (a hold —
#    captured only on success, never on failure). Pro and first-migration-free runs start
#    immediately with NO checkout_url, so those are fully headless.
[ -n "$checkout" ] && echo "Authorize the hold: $checkout"

# 4. Poll until the run finishes.
while :; do
  status=$(curl -s "$BASE/api/migrations/$id" "${auth[@]}" | jq -r '.status')
  echo "status: $status"
  case "$status" in completed|failed) break;; esac
  sleep 3
done

# 5. (Optional) Read the timeline.
curl -s "$BASE/api/migrations/$id/logs" "${auth[@]}" | jq -r '.data[].message'
```

> **Fully headless?** Yes for **Pro** and your **first-migration-free** run (no payment step). A
> **pay-as-you-go** run needs the one-time Stripe hold authorized in a browser — after that the
> whole thing is API-driven.

The rest of this page is the reference for each step.

## Create a migration

```bash
curl -X POST https://dev.dbmigratepro.com/api/migrations \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "source_url": "mysql://user:pass@source-host:3306/app",
    "target_url": "postgresql://user:pass@target-host:5432/app"
  }'
```

Fields:

| Field | Required | Description |
|---|---|---|
| `source_url` | yes | Connection URL of the database to migrate **from** (MySQL or PostgreSQL). |
| `target_url` | yes | Connection URL of the database to migrate **to**. |
| `estimate_id` | no | ID of a prior price quote, to tie the run to a shown estimate. |

The engine auto-detects whether this is a **same-engine transfer** or a **cross-engine
conversion** from the two URLs — you don't pick.

### Response

- **Pay-as-you-go:** the migration is created as `awaiting_payment` and the response
  includes a `checkout_url`. Send the user there to authorize payment; the migration
  starts automatically once the hold is placed. You're only charged on success.

  ```json
  {
    "message": "Payment required to start migration",
    "migration": { "id": "0191e5c2-6a4b-71f2-b3c9-2d1e4f8a9c07", "status": "awaiting_payment", "...": "..." },
    "amount": 8.42,
    "checkout_url": "https://checkout.stripe.com/..."
  }
  ```

- **Pro subscriber:** it starts immediately, no per-migration charge:

  ```json
  {
    "message": "Migration started — included in your Pro plan",
    "migration": { "id": "0191e5c2-6a4b-71f2-b3c9-2d1e4f8a9c07", "status": "pending" },
    "covered_by_plan": true
  }
  ```

- **First migration free** (non-Pro, source under 1 GB): your first successful migration is on us —
  it starts immediately with no hold and no `checkout_url`:

  ```json
  {
    "message": "Migration started — your first migration is on us (under 1 GB)",
    "migration": { "id": "0191e5c2-6a4b-71f2-b3c9-2d1e4f8a9c07", "status": "pending" },
    "covered_by_plan": true,
    "free": true
  }
  ```

Detect the case in code: if the response has a `checkout_url`, authorize it; otherwise the run has
already started (Pro or free).

> The migration `id` is an opaque string (a UUID) — capture it from the create response and pass it
> back verbatim; don't assume it's numeric.

## Check migration status

```bash
curl https://dev.dbmigratepro.com/api/migrations/0191e5c2-6a4b-71f2-b3c9-2d1e4f8a9c07 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Accept: application/json"
```

Key `status` values you'll see:

| Status | Meaning |
|---|---|
| `awaiting_payment` | Created; waiting for the payment hold to be authorized. |
| `pending` / `in_progress` | Queued or actively running. |
| `completed` | Done — schema, data, indexes and foreign keys applied; row counts verified. |
| `failed` | Stopped and failed closed. Not charged. Inspect logs and retry. |

## Other endpoints

| Method & path | Purpose |
|---|---|
| `GET /api/migrations` | List your migrations. |
| `GET /api/migrations/{id}` | Get one migration. |
| `GET /api/migrations/{id}/logs` | Fetch the migration's log timeline. |
| `POST /api/migrations/{id}/checkout` | (Re)start Stripe Checkout for one that's awaiting payment. |
| `POST /api/migrations/{id}/retry` | Re-queue a failed migration. |
| `DELETE /api/migrations/{id}` | Delete a migration. |

## Polling for completion

Poll `GET /api/migrations/{id}` until `status` is `completed` or `failed`. A typical loop
waits a few seconds between checks. Migrations of small databases finish in seconds;
larger ones scale with size.
