Docs
BlogHomeStart building

Export & Import

Clone a project, or hand it to another account.


Export returns a secret importCode; whoever holds it can import the project into a new one (on any account). Use it to duplicate a project, to start projects from a template, or to give a project to a client.

What travels: the current code, the database schema and migrations, the live data with includeRecords, the uploaded files with includeFiles, and the scheduled jobs. What never travels: secrets, and the app's users and sign-in data (users, passwords, sessions) — their tables arrive empty, so everyone signs up again on the copy; with includeRecords, rows owned by a user follow that relation's delete rule.

Clone: export the source → Create Project with the new project's projectId and "sourceImportCode": "<code>" → Import Project into it → poll Get Project until importInProgress is null.

Choosing the new project's id: an import keeps the id of the project it goes into, so the projectId you create it with is the imported project's id (legacy or new-engine code alike). A migration takes it as projectId in its body.

#Export Project

POST/api/v1/vcaas/projects/:projectId/exportapi-key4 credits

Create an export and get its code. At most 5 exports an hour per project. The records exported are the live database's (a project never published exports none).

Path parameters

ParameterTypeDescription
projectIdstringThe project to export

Body parameters

FieldTypeRequiredDescription
includeRecordsbooleanNoAlso export the live data (default false: schema only)
includeFilesbooleanNoAlso export the uploaded files (default true)

Response fields

FieldTypeDescription
data.importCodestringSecret: anyone with it can import the project
data.includeRecordsbooleanData was included
data.includeFilesbooleanFiles were included
data.filesobject | null{ count, bytes } of the files exported
data.messagestringConfirmation

Example request

curl -X POST \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"includeRecords":true}' \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/export
Success · 200 OK
json
{ "errors": null, "data": { "importCode": "barber-bookings-export-project-8f3b1c9a47e2d650b9114af0c7e3a2d1", "includeRecords": true, "message": "Export ready. Save the importCode (keep it secret) and pass it to import-project as importCode.", "includeFiles": true, "files": { "count": 12, "bytes": 3145728 } } }

Error codes

CodeHTTPMeaning
PROJECT_EXPORT_LIMIT_REACHED4005 exports in the last hour
INSUFFICIENT_CREDITS402Not enough credits

#Import Project

POST/api/v1/vcaas/projects/:projectId/importapi-key12 credits

Import an export into this project, which must be new and empty. The imported project keeps this project's id: to give it an id of your choice, create the project with that id first (with sourceImportCode, so it is made on the code's engine). The data fills both the development and the live database; then the code is built. Runs in the background (a few minutes): poll Get Project until importInProgress is null. Prompts are refused (IMPORT_IN_PROGRESS) meanwhile.

Path parameters

ParameterTypeDescription
projectIdstringThe target project; the imported project keeps this id

Body parameters

FieldTypeRequiredDescription
importCodestringYesThe code from Export Project

Response fields

FieldTypeDescription
data.projectIdstringThe target project
data.statusstring"importing"
data.messagestringHow to follow it

Example request

curl -X POST \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"importCode":"barber-bookings-export-project-8f3b1c9a47e2d650b9114af0c7e3a2d1"}' \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings-copy/import
Success · 200 OK
json
{ "errors": null, "data": { "projectId": "barber-bookings-copy", "status": "importing", "message": "Import started. Poll GET /api/v1/vcaas/projects/barber-bookings-copy every 10-15s until importInProgress is null, then until agentServerStatus='Active'. Prompting is blocked until the import finishes." } }

Error codes

CodeHTTPMeaning
MISSING_IMPORT_CODE400importCode is missing
INVALID_IMPORT_CODE400Not an import code, or the export no longer exists
PROJECT_NOT_IMPORTABLE400The target already has content; use a new project
ENGINE_MISMATCH409The code comes from a project on the legacy engine and can only be imported into a legacy project
IMPORT_IN_PROGRESS409An import is already running
AGENT_RUNNING409A prompt is running
INSUFFICIENT_CREDITS402Not enough credits

#Migrate to the New Engine

POST/api/v1/vcaas/projects/:projectId/migrate-to-v2api-key50 credits

Move a project built on the legacy engine to the new engine. It makes a new project, <projectId>v2 (with a few random letters added when that name is taken) or the id you pass as projectId in the body, and leaves the legacy project exactly as it is: it keeps working and serving its users until you switch. Needs the Starter plan or above.

What the new project gets:

  • The data: the legacy database (its tables, fields and every record) is kept as a private file of the new project, legacy-database.json, and every uploaded file the records point at (a file field, or a storage link kept as text) is copied into the new project's storage (the records point at the copies).
  • The secrets: the project's own secrets are copied with the same development and live values, stored encrypted on the new project before its first prompt, so the app reads them as before (process.env.NAME on the server). Their values never appear in the response or the agent's prompt; the response lists their names. The legacy Totalum API key and BETTER_AUTH_SECRET are not copied: the new project has its own.
  • The code: the AI agent receives the legacy code, the legacy database structure and the legacy SDK reference, and rebuilds the same app on the new engine (pages, flows, design, sign-in; its users keep their accounts). It designs the SQL tables and moves every record into them with a data migration generated by code from legacy-database.json: on the development database during the run, on the live database when you first publish the new project. The databases hold only the app's own tables; legacy-database.json stays in the project's files.

It answers once the new project exists and its first prompt has started (this call can take a minute). Follow the agent with Get Agent Status on the new projectId. The 50 credits are charged once; the agent's work is then billed like any prompt. If the migration cannot start, nothing is charged and no project is kept. Custom domains, GitHub and Figma connections do not move: set them again on the new project. A secret the new engine cannot take (a name it reserves, such as one starting with TOTALUM_, or an empty value) is listed in secretsNotCopied: set it again under another name.

Path parameters

ParameterTypeDescription
projectIdstringThe legacy project to migrate

Body parameters

FieldTypeRequiredDescription
projectIdstringNoThe new project's id, with the rules of Create Project. A taken id is refused (PROJECT_ALREADY_EXISTS), never changed. Default <projectId>v2

Response fields

FieldTypeDescription
data.projectIdstringThe new project
data.sourceProjectIdstringThe legacy project (unchanged)
data.engineVersionstring"v2"
data.creditsChargednumberCredits charged for the migration
data.migratedobject{ tables, records, files, filesNotCopied, codeFiles, codeFilesSkipped, secrets, secretsNotCopied }; secrets and secretsNotCopied are secret names
data.agentobjectThe first prompt: { started, status, runId, expectedMinutes, message }

Example request

curl -X POST \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"projectId":"barber-bookings-new"}' \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/migrate-to-v2
Success · 200 OK
json
{ "errors": null, "data": { "projectId": "barber-bookings-new", "sourceProjectId": "barber-bookings", "engineVersion": "v2", "creditsCharged": 50, "migrated": { "tables": 6, "records": 412, "files": 38, "filesNotCopied": 0, "codeFiles": 126, "codeFilesSkipped": 0, "secrets": ["STRIPE_SECRET_KEY", "RESEND_API_KEY"], "secretsNotCopied": [] }, "agent": { "started": true, "status": "init", "runId": "run_01m4b8w0d9", "expectedMinutes": 18, "message": "Process started. …" } } }

Error codes

CodeHTTPMeaning
PLAN_REQUIRED403The account is below the Starter plan (errorDetails.upgradePlan)
INVALID_PROJECT_NAME400The body's projectId breaks the id rules (also INVALID_PROJECT_NAME_LENGTH, RESERVED_PROJECT_NAME)
PROJECT_ALREADY_EXISTS409The body's projectId is taken
INSUFFICIENT_CREDITS402Fewer than 50 credits
ENGINE_MISMATCH409The project is already on the new engine
PROJECT_NOT_FOUND404No legacy project with this id on your account
MAX_PROJECTS_REACHED403Your plan's project limit is reached (the migration makes a new project)
MIGRATION_TOO_LARGE413Over 20,000 records, 20 MB of data, 20 MB of code or 500 files: contact support
RATE_LIMITED429This project is already being migrated
MIGRATION_START_FAILED502The migration could not start; nothing was charged, try again
End of Migrate to the New Engine Back to top