> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pocketsflow.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrations

> Import products, customers, purchase history and email subscribers from Gumroad, Lemon Squeezy, Stan Store or any CSV. Volume plan and above.

An **import** (a migration job) moves a store from another platform into
yours: upload the platform's CSV exports or pull with its API token, check
every row, dry-run, then start a background run that is idempotent and
resumable. Purchase history is saved as **imported records**, never as sales:
it never counts toward revenue, fees, payouts or webhooks. Subscribers are
never emailed by the import.

<Note>
  Creating and running an import needs the **Volume** plan. Below it, every
  write answers `403` with `code: FEATURE_LOCKED` and
  `details.requiredPlan: "volume"` — API keys and MCP included. Reading an
  import, its report and its row CSV, and cancelling, always work.
</Note>

## Endpoints

| Method | Path | What it does |
| - | - | - |
| `GET` | `/migrations/platforms` | Sources, the CSV columns each export uses, API support |
| `GET` | `/migrations` | Your imports, newest first (`page`, `pageSize` ≤ 100) |
| `POST` | `/migrations` | Create a draft import (optionally with `files`) |
| `GET` | `/migrations/{id}` | The import: files, mapping, dry run, status, progress |
| `POST` | `/migrations/{id}/files` | Add (or replace) one CSV export |
| `PUT` | `/migrations/{id}/files/{kind}/mapping` | Fix the column mapping; rows are validated again |
| `DELETE` | `/migrations/{id}/files/{kind}` | Remove a file |
| `GET` | `/migrations/{id}/rows` | Rows of one kind with errors (`kind`, `filter`, `page`) |
| `POST` | `/migrations/{id}/connect` | Pull from Gumroad / Lemon Squeezy with a token |
| `POST` | `/migrations/{id}/dry-run` | What the run will create, skip and leave out |
| `POST` | `/migrations/{id}/start` | Queue the background run (`202`) |
| `POST` | `/migrations/{id}/cancel` | Stop after the current batch |
| `GET` | `/migrations/{id}/report` | Per-kind results, products that need a file |
| `GET` | `/migrations/{id}/errors.csv` | Every row not imported cleanly, as CSV |

`source` is `gumroad`, `lemonsqueezy`, `stan` or `csv`. `kind` is
`products`, `customers`, `orders` (purchase history) or `subscribers`.

## Example: a Gumroad move

```bash theme={"system"}
# 1. Create the import with the sales and followers exports
curl -X POST https://api.pocketsflow.com/migrations \
  -H "Authorization: Bearer $POCKETSFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile sales sales.csv --rawfile followers followers.csv \
    '{source:"gumroad", files:[{kind:"orders", filename:"sales.csv", csv:$sales},
                                {kind:"subscribers", filename:"followers.csv", csv:$followers}]}')"

# 2. Products: Gumroad has no product CSV, so pull them with a token (never stored)
curl -X POST https://api.pocketsflow.com/migrations/$ID/connect \
  -H "Authorization: Bearer $POCKETSFLOW_API_KEY" \
  -H "Content-Type: application/json" -d '{"token":"<gumroad access token>"}'

# 3. Check the rows with errors, then dry-run
curl "https://api.pocketsflow.com/migrations/$ID/rows?kind=orders&filter=problems" \
  -H "Authorization: Bearer $POCKETSFLOW_API_KEY"
curl -X POST https://api.pocketsflow.com/migrations/$ID/dry-run \
  -H "Authorization: Bearer $POCKETSFLOW_API_KEY"

# 4. Start, then poll until status is "completed"
curl -X POST https://api.pocketsflow.com/migrations/$ID/start \
  -H "Authorization: Bearer $POCKETSFLOW_API_KEY"
curl https://api.pocketsflow.com/migrations/$ID -H "Authorization: Bearer $POCKETSFLOW_API_KEY"
```

## Dry run

```json theme={"system"}
{
  "computedAt": "2026-10-17T09:12:00.000Z",
  "canStart": true,
  "subscriberLimit": null,
  "kinds": {
    "products": { "total": 24, "invalid": 0, "skipped": 0, "create": 22, "existing": 2, "filesToDownload": 17, "fileNeeded": 5 },
    "orders": { "total": 4290, "invalid": 4, "skipped": 0, "create": 4286, "alreadyImported": 0, "newCustomers": 1840, "refunded": 61 },
    "subscribers": { "total": 6512, "invalid": 12, "noConsent": 80, "create": 6420, "active": 6105, "unsubscribed": 315, "existing": 0, "suppressed": 0, "overLimit": 0, "activeNow": 214 }
  },
  "notes": ["Purchase history is saved as imported records. It never counts as sales, revenue, fees or payouts."]
}
```

## How the run behaves

* **Order:** products, then customers, then purchase history (matched to the
  products by source id or name), then subscribers.
* **Idempotent:** products match on the platform's product id or the name,
  customers and subscribers on the email, purchases on the platform's order
  id (or a stable hash of the row). Nothing is created twice.
* **Resumable:** the run holds a lease and renews it per batch; if it stops,
  the sweep resumes it within a minute from the first row not yet done.
  `start` on a `failed` or `canceled` import imports the rest.
* **Existing data wins:** a product, customer or subscriber you already have
  is skipped, never overwritten. A subscriber's status is never changed.
* **Consent:** each imported subscriber carries `consent` (`source` such as
  `gumroad_csv` or `lemonsqueezy_api`, the platform's wording, the opt-in
  date). Statuses without consent (`archived`, `requires_verification`,
  unknown) are left out; `unsubscribed` / `bounced` are imported with that
  status. Addresses on the bounce / spam complaint list are left out.
* **Files:** product files download from public `https://` links only (no
  private addresses, re-checked on every redirect), up to your plan's upload
  size, for deliverable file types only. Otherwise the product is marked
  `fileNeeded` with the reason in the report.

## White-glove imports

Our team can run the import for a Volume creator from the admin dashboard.
Those imports show up in `GET /migrations` with `createdBy.type: "admin"`
and work exactly like yours.

## MCP

`list_import_platforms`, `list_import_jobs`, `create_import_job`,
`get_import_job`, `add_import_file`, `update_import_mapping`,
`list_import_rows`, `connect_import_account`, `dry_run_import_job`,
`start_import_job`, `cancel_import_job`, `get_import_report`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.