Imports
Start, follow and resume an import into one environment of a project (see Importing an app for what comes across). The CLI’s flatstack import and the MCP tools import_project and get_import_status use these routes.
An import writes your app users’ data, so starting or resuming one needs data:write (owners, admins, and tokens with that scope); following one needs data:read.
| Request | Needs | What it does |
|---|---|---|
POST /v1/projects/{projectId}/imports |
data:write |
Starts an import, or with dryRun: true returns its report and writes nothing. |
GET /v1/projects/{projectId}/imports |
data:read |
{ imports }: the latest 20, newest first. |
GET /v1/projects/{projectId}/imports/{importId} |
data:read |
The import’s status and progress. |
GET /v1/projects/{projectId}/imports/{importId}/report |
data:read |
{ status, report, files }. ?format=markdown returns the migration report alone. |
POST /v1/projects/{projectId}/imports/{importId}/resume |
data:write |
Runs the next step. A completed import is returned as it is. |
Starting
{
"source": "supabase",
"environment": "production",
"dryRun": true,
"credentials": {
"databaseUrl": "postgresql://postgres.abcd:password@aws-0-eu-west-2.pooler.supabase.com:5432/postgres",
"projectUrl": "https://abcd.supabase.co",
"serviceRoleKey": "eyJ...",
"s3": { "accessKeyId": "...", "secretAccessKey": "...", "region": "eu-west-2" }
},
"functions": [{ "name": "send-digest", "files": [{ "path": "index.ts", "content": "..." }] }]
}
source:supabase,lovableorclerk(below).environmentdefaults toproduction.credentials.databaseUrlis required;projectUrlwithserviceRoleKey, ors3, lets the import copy files.functions: the edge functions in your repo’ssupabase/functions/, copied tofunctions/legacy/.
A dry run answers 200 with { dryRun: true, engine, blocked, summary, issues, report, files }. files is everything the import writes to your repo, MIGRATION_REPORT.md included; the real run’s report is the same, word for word.
A real run answers 201 with the import once its first step finished or failed, or 202 while it’s still running. It copies for about 20 seconds per request.
{
"dryRun": false,
"id": "imp_…",
"source": "supabase",
"environment": "production",
"engine": "d1",
"status": "running",
"phase": "data",
"position": "copying table todos",
"progress": { "users": 4, "usersSkipped": 0, "signInLinks": 4, "rows": { "profiles": 3 }, "objects": 0, "objectsSkipped": 0, "bytes": 0 },
"summary": { "tables": 4, "rows": 10, "users": 4, "objects": 2 },
"review": 14,
"error": null,
"canResume": true
}
status is running, completed or failed. phase is schema, users, data, foreign_keys, files or done. A failed import has error: { code, message }; resuming carries on from where it stopped. usersSkipped counts users whose email address already belonged to someone in the project; they aren’t imported.
Clerk
{
"source": "clerk",
"dryRun": true,
"credentials": {
"secretKey": "sk_live_...",
"passwords": [{ "userId": "user_2abc", "digest": "$2b$10$...", "hasher": "bcrypt" }],
"webhooks": ["https://example.com/api/clerk-webhook"]
}
}
credentials.secretKeyis required: the Clerk application’s secret key.passwords: theid,password_digestandpassword_hashercolumns of the user export CSV from Clerk’s dashboard. Onlybcrypthashes are kept.flatstack import clerk --export <file.csv>reads the file for you.webhooks: your Clerk webhook endpoints, for the report.
A Clerk import’s report file is MIGRATION_REPORT_CLERK.md. Its phase is schema, users, owners, organizations, memberships or done, and its progress is { users, usersMerged, usersSkipped, signInLinks, ownerIdsRewritten, organizations, memberships }. In the owners phase the import changes merged users’ Clerk IDs (user_…) to their Flatstack IDs in your own tables’ text columns; ownerIdsRewritten counts the rows changed, and the report lists each column. usersMerged counts Clerk users merged into a user already in the project with the same verified email; usersSkipped counts those whose matching address was unverified on one side, who aren’t imported.
Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_failed |
The body doesn’t match. Credential values are never echoed back. |
| 409 | import_running |
Another import into the environment is running, or another request is running this one’s step. |
| 409 | import_expired |
The import failed more than a day ago, so its credentials were deleted. Start it again; nothing is copied twice. |
| 422 | import_blocked |
Something stops the import, listed in details.problems: a table that already exists, or a column D1 can’t hold. Nothing was written. |
| 502 | import_source_unreachable |
The source database couldn’t be read. details.error has the reason, with credentials removed. |
Starting, planning, resuming, finishing and failing are written to the audit log (import.started, import.planned, import.resumed, import.completed, import.failed), without credentials. A project in another account, or an environment a deploy token wasn’t made for, answers 404 not_found.
As Markdown: api/imports.md