Docs
BlogHomeStart building

Database Backups & Restore

Nothing in your database is ever lost for good.


Time Travel keeps every minute of the last 30 days of each database; backups are SQL dumps you can download; a restore point is a bookmark Totalum records before a change: at the start of every agent run (on the development database) and before every restore. A restore puts one database back in place — every write made since is lost — so it always takes a backup and a restore point first, never runs during an agent run and is never done automatically. Every long step answers 202 and runs in the background. A restore of the live database never goes back past the database changes the published site needs: a point before the publish that last changed the live database's structure answers 409 RESTORE_BEFORE_LIVE_MIGRATION with errorDetails.earliestPoint, the first point allowed. To go back further, unpublish the site first; the next publish applies those changes again.

Scheduled backups (paid plans). The live database of every project on a paid plan is backed up once a night, in the night of the region where the database lives. Nothing to set up. A night with no write since the last backup makes no new copy: schedule.lastStatus is "skipped_unchanged" and that backup stays current. Each backup is one gzip SQL dump with its SHA-256, kept by plan:

PlanDailyWeeklyMonthly
Starter7 days4 weeks–
Builder7 days4 weeks3 months
Professional7 days4 weeks6 months
Enterprise7 days4 weeks12 months

The first backup of each week is kept as the weekly one, the first of each month as the monthly one. The backups count as storage of the project, like its files; there is no other fee. After a downgrade the older backups are trimmed to the new plan at the next run; on the free plan no new backup is made and the existing ones stay until they expire. A database of several gigabytes is backed up weekly instead of nightly (schedule.frequency is "weekly"), copied table by table while the site keeps running. The development database is not backed up on a schedule: Time Travel and the restore points cover it. A deleted project's last backup is deleted 30 days later.

#List Backups

GET/api/v1/vcaas/projects/:projectId/database/backupsapi-keyFree

The project's backups, newest first, the schedule of the live database's backups and the Time-Travel window.

Path parameters

ParameterTypeDescription
projectIdstringThe project ID

Response fields

FieldTypeDescription
data.backups[].idstringBackup ID
data.backups[].envstring"live" or "dev"
data.backups[].kindstring"daily", "weekly" or "monthly" (scheduled), "manual", or "pre_restore" (taken before a restore)
data.backups[].statusstring"running", "complete" or "failed" (error says why)
data.backups[].sizeBytesnumber | nullSize of the stored file
data.backups[].bookmarkstring | nullThe restore point taken with it
data.backups[].createdAtstringISO time
data.backups[].expiresAtstring | nullWhen a scheduled backup is deleted; null for the others
data.schedule.enabledbooleantrue on a paid plan
data.schedule.frequencystring"daily", or "weekly" for a database of several gigabytes
data.schedule.nextRunAtstring | nullThe next run
data.schedule.lastRunAtstring | nullThe last run
data.schedule.lastStatusstring | null"complete", "skipped_unchanged" (no write since lastBackupAt) or "failed" (lastError says why; the next night tries again)
data.schedule.lastBackupAtstring | nullThe newest scheduled backup
data.schedule.retentionobject | null{ daily, weekly, monthly }: days, weeks and months each kind is kept; null on the free plan
data.timeTravelobject{ availableFrom, availableTo } — the window of Restore to a Point in Time

Example request

curl -H "api-key: tlm_sk_your_key" \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/database/backups
Success · 200 OK
json
{ "errors": null, "data": { "backups": [{ "id": "bkp_01k6xg", "env": "live", "kind": "daily", "status": "complete", "sizeBytes": 18233, "bookmark": "00000085-00000001-4f1c", "createdAt": "2026-10-08T02:10:00.000Z", "expiresAt": "2026-10-15T02:10:00.000Z" }], "schedule": { "enabled": true, "frequency": "daily", "nextRunAt": "2026-10-09T02:10:00.000Z", "lastRunAt": "2026-10-08T02:10:00.000Z", "lastStatus": "complete", "lastBackupAt": "2026-10-08T02:10:00.000Z", "retention": { "daily": 7, "weekly": 4, "monthly": 3 } }, "timeTravel": { "availableFrom": "2026-09-08T12:00:00.000Z", "availableTo": "2026-10-08T12:00:00.000Z" } } }

Error codes

CodeHTTPMeaning

#Create Backup

POST/api/v1/vcaas/projects/:projectId/database/backupsapi-key2 credits

Dump one database now. One manual backup per 10 minutes per project. Poll List Backups until status is "complete".

Path parameters

ParameterTypeDescription
projectIdstringThe project ID

Body parameters

FieldTypeRequiredDescription
envstringNo"live" (default) or "dev"

Response fields

FieldTypeDescription
data.backupIdstringThe backup being written
data.statusstring"running"
data.creditsChargednumber2 (BACKUP_CREATE_MANUAL in Get Credit Costs)

Example request

curl -X POST \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"env":"live"}' \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/database/backups
Success · 202 Accepted
json
{ "errors": null, "data": { "backupId": "bkp_01k6xj", "status": "running", "creditsCharged": 2 } }

Error codes

CodeHTTPMeaning
BACKUP_RATE_LIMITED429A manual backup was made less than 10 minutes ago
DEV_DATABASE_NOT_FOUND404env is "dev" and the project has no development database

#Download Backup

GET/api/v1/vcaas/projects/:projectId/database/backups/:backupId/downloadapi-keyFree

A 15-minute link to a complete backup as standard SQLite SQL (every row, including password hashes and sessions; uploaded files are not included). A scheduled backup is gzip-compressed (.sql.gz).

Path parameters

ParameterTypeDescription
projectIdstringThe project ID
backupIdstringFrom List Backups

Response fields

FieldTypeDescription
data.urlstringSigned download URL (15 minutes)
data.fileNamestringSuggested file name
data.sizeBytesnumberSize of the file
data.sha256stringSHA-256 of the file
data.importRecipestringsqlite3 my-project.sqlite < <fileName>, or gunzip -c <fileName> | sqlite3 my-project.sqlite for a .sql.gz

Example request

curl -H "api-key: tlm_sk_your_key" \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/database/backups/bkp_01k6xj/download
Success · 200 OK
json
{ "errors": null, "data": { "url": "https://files.totalum.app/backups/…", "fileName": "barber-bookings-live-2026-10-08.sql", "sizeBytes": 18233, "sha256": "3b1f…", "importRecipe": "sqlite3 my-project.sqlite < barber-bookings-live-2026-10-08.sql" } }

Error codes

CodeHTTPMeaning
BACKUP_NOT_FOUND404No such backup, or it has no complete dump

#Restore Backup

POST/api/v1/vcaas/projects/:projectId/database/backups/:backupId/restoreapi-key4 credits

Put a database back to one of its backups (scheduled, manual or pre-restore). A backup of the current state is taken first; then the database is emptied, the backup is loaded and its tables, row counts and migration history are checked against the backup. If the load fails, the database goes back to the state before the restore. Every write made since the backup is lost. Poll GET /projects/:projectId/state until the operation ends.

Path parameters

ParameterTypeDescription
projectIdstringThe project ID
backupIdstringFrom List Backups, with status "complete"

Body parameters

FieldTypeRequiredDescription
confirmstringYesThe project ID, typed to confirm

Response fields

FieldTypeDescription
data.statusstring"restoring"
data.envstringThe database restored (the backup's own)
data.restoredTostringbackup:<backupId> (on a database moved to Turso: turso:<ISO time of the backup>)
data.previousBookmarkstringThe point taken just before — restoring to it undoes this restore
data.preRestoreBackupIdstringThe backup taken first; the restore runs only once it is complete
data.operationobject{ kind: "backupRestore", id } — progress in GET /projects/:projectId/state
data.creditsChargednumber4 (BACKUP_RESTORE in Get Credit Costs)

Example request

curl -X POST \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"confirm":"barber-bookings"}' \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/database/backups/bkp_01k6xj/restore
Success · 202 Accepted
json
{ "errors": null, "data": { "env": "live", "status": "restoring", "restoredTo": "backup:bkp_01k6xj", "previousBookmark": "00000091-00000002-77aa", "preRestoreBackupId": "bkp_01k6xk", "operation": { "kind": "backupRestore", "id": "op_01k6xm" }, "creditsCharged": 4 } }

Error codes

CodeHTTPMeaning
CONFIRMATION_REQUIRED400confirm is not the project ID
VALIDATION_ERROR400On a database moved to Turso, the backup is from before the move (errorDetails.availableFrom): download it, or restore a point in time from availableFrom on. Nothing is charged
BACKUP_NOT_FOUND404No such backup, or it has no complete dump
RESTORE_BEFORE_LIVE_MIGRATION409The live backup is older than the database changes the published site needs; unpublish first
BACKUP_TOO_LARGE_FOR_IMPORT409The backup is too large to load in place: download it, or use Restore to a Point in Time
AGENT_RUNNING409The agent is running
OPERATION_IN_PROGRESS409Another operation is in progress

#Delete Backup

DELETE/api/v1/vcaas/projects/:projectId/database/backups/:backupIdapi-keyFree

Delete a manual or pre-restore backup. Scheduled backups are deleted by their retention.

Path parameters

ParameterTypeDescription
projectIdstringThe project ID
backupIdstringFrom List Backups

Response fields

FieldTypeDescription
data.deletedbooleantrue

Example request

curl -X DELETE -H "api-key: tlm_sk_your_key" \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/database/backups/bkp_01k6xj
Success · 200 OK
json
{ "errors": null, "data": { "deleted": true } }

Error codes

CodeHTTPMeaning
BACKUP_NOT_DELETABLE409Only manual and pre-restore backups can be deleted
BACKUP_NOT_FOUND404No such backup

#List Restore Points

GET/api/v1/vcaas/projects/:projectId/database/bookmarksapi-keyFree

Every restore point, newest first. Query runId to see one agent run's.

Path parameters

ParameterTypeDescription
projectIdstringThe project ID

Response fields

FieldTypeDescription
data.databaseCasestring"dev+live": the agent works on the development database
data.items[].envstringThe database
data.items[].bookmarkstringSend it to Restore to a Restore Point
data.items[].atstringISO time it was taken
data.items[].kindstring"run" or "pre_restore"
data.items[].runIdstring | undefinedThe agent run that took it

Example request

curl -H "api-key: tlm_sk_your_key" \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/database/bookmarks
Success · 200 OK
json
{ "errors": null, "data": { "databaseCase": "dev+live", "items": [{ "env": "dev", "bookmark": "00000085-00000001-4f1c", "at": "2026-10-08T11:10:02.000Z", "kind": "run", "runId": "run_01k6xc4n7d" }] } }

Error codes

CodeHTTPMeaning

#Restore to a Restore Point

POST/api/v1/vcaas/projects/:projectId/database/restoreapi-keyFree

Put one database back to a restore point from List Restore Points — for example the moment before the agent changed it. Free: it undoes an agent step.

Path parameters

ParameterTypeDescription
projectIdstringThe project ID

Body parameters

FieldTypeRequiredDescription
bookmarkstringYesFrom List Restore Points
envstringNo"live" (default) or "dev"
confirmstringYesThe project ID, typed to confirm

Response fields

FieldTypeDescription
data.statusstring"restoring"
data.envstringThe environment restored
data.restoredTostringThe Time-Travel bookmark restored to
data.previousBookmarkstringThe point taken just before — restoring to it undoes this restore
data.preRestoreBackupIdstringThe backup taken first; the restore runs only once it is complete
data.operationobject{ kind: "backupRestore", id } — progress in GET /projects/:projectId/state

Example request

curl -X POST \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"bookmark":"00000085-00000001-4f1c","env":"dev","confirm":"barber-bookings"}' \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/database/restore
Success · 202 Accepted
json
{ "errors": null, "data": { "env": "dev", "status": "restoring", "restoredTo": "00000085-00000001-4f1c", "previousBookmark": "00000091-00000002-77aa", "preRestoreBackupId": "bkp_01k6xn", "operation": { "kind": "backupRestore", "id": "op_01k6xp" } } }

Error codes

CodeHTTPMeaning
BOOKMARK_NOT_FOUND404The bookmark is not a restore point of this database
RESTORE_BEFORE_LIVE_MIGRATION409The live point lies before database changes the published site needs (errorDetails: deploymentId, migrations, earliestPoint); choose earliestPoint or later, or unpublish first
CONFIRMATION_REQUIRED400confirm is not the project ID
AGENT_RUNNING409An agent run is in progress
OPERATION_IN_PROGRESS409Another operation (a restore, a publish…) is running
DEV_DATABASE_NOT_FOUND404env is "dev" and the project has no development database
End of Restore to a Restore PointNext endpointPOSTRestore to a Point in Time

#Restore to a Point in Time

POST/api/v1/vcaas/projects/:projectId/database/time-travel/restoreapi-key4 credits

Put one database back to any minute of the last 30 days (on a database moved to Turso: from the move on, timeTravel.availableFrom in List Backups).

A refused request costs nothing. A restore that is accepted and then fails gives its 4 credits back; the same holds for Restore a Backup.

Path parameters

ParameterTypeDescription
projectIdstringThe project ID

Body parameters

FieldTypeRequiredDescription
envstringNo"live" (default) or "dev"
timestampstringYesISO time within the last 30 days
confirmstringYesThe project ID, typed to confirm

Response fields

FieldTypeDescription
data.statusstring"restoring"
data.envstringThe environment restored
data.restoredTostringThe Time-Travel bookmark restored to
data.previousBookmarkstringThe point taken just before — restoring to it undoes this restore
data.preRestoreBackupIdstringThe backup taken first; the restore runs only once it is complete
data.operationobject{ kind: "backupRestore", id } — progress in GET /projects/:projectId/state
data.creditsChargednumber4 (BACKUP_RESTORE in Get Credit Costs)

Example request

curl -X POST \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"env":"live","timestamp":"2026-10-08T09:00:00.000Z","confirm":"barber-bookings"}' \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/database/time-travel/restore
Success · 202 Accepted
json
{ "errors": null, "data": { "env": "live", "status": "restoring", "restoredTo": "00000080-00000003-1a2b", "previousBookmark": "00000091-00000002-77aa", "preRestoreBackupId": "bkp_01k6xq", "operation": { "kind": "backupRestore", "id": "op_01k6xr" }, "creditsCharged": 4 } }

Error codes

CodeHTTPMeaning
VALIDATION_ERROR400timestamp is outside the last 30 days or, on a database moved to Turso, before the move (its point-in-time history starts there; errorDetails.availableFrom, also in GET …/database/backups?env= as timeTravel.availableFrom)
RESTORE_BEFORE_LIVE_MIGRATION409The live point lies before database changes the published site needs (errorDetails: deploymentId, migrations, earliestPoint); choose earliestPoint or later, or unpublish first
RESTORE_VERIFY_FAILED409Time Travel resolved timestamp to a state older or newer than a restore point recorded around that time; nothing was restored (errorDetails: timestamp, resolvedBookmark, restorePoint). Restore to that recorded point instead: its bookmark with Restore to a Restore Point
CONFIRMATION_REQUIRED400confirm is not the project ID
AGENT_RUNNING409An agent run is in progress
OPERATION_IN_PROGRESS409Another operation (a restore, a publish…) is running
DEV_DATABASE_NOT_FOUND404env is "dev" and the project has no development database
End of Restore to a Point in TimeNext endpointPOSTMove to Turso

#Move to Turso

POST/api/v1/vcaas/projects/:projectId/database/migrate-providerapi-key50 credits

Move the project's database to Turso, (a managed Turso database, run by Totalum). One way: it cannot be moved back. The 50 credits are refunded in full when the move fails before the switch. Both databases, live and dev, move. Available on the Builder plan and above.

Path parameters

ParameterTypeDescription
projectIdstringThe project ID

Body parameters

FieldTypeRequiredDescription
tostringYes"turso"
confirmstringYesThe project ID, typed to confirm

Response fields

FieldTypeDescription
data.operationobject{ kind: "dbProviderMigration", id, status, startedAt, to, envs, estimateSec, databaseBytes } — follow it in GET /projects/:projectId/state (below)

Example request

curl -X POST \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"to":"turso","confirm":"barber-bookings"}' \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/database/migrate-provider
Success · 202 Accepted
json
{ "errors": null, "data": { "operation": { "kind": "dbProviderMigration", "id": "op_01k6xs", "status": "running", "startedAt": "2026-10-08T12:00:00.000Z", "to": "turso", "envs": ["live", "dev"], "estimateSec": 240, "databaseBytes": 2457600 } } }

Error codes

CodeHTTPMeaning
CONFIRMATION_REQUIRED400confirm is not the project ID
MIGRATION_NOT_REVERSIBLE400to is not "turso"
PLAN_REQUIRED403The account's plan is below Builder
ALREADY_ON_PROVIDER409The project is already on Turso
INSUFFICIENT_CREDITS402Fewer than 50 credits
AGENT_RUNNING409An agent run is in progress

Following the move

It takes about 4 minutes per GB. While it runs, GET /api/v1/vcaas/projects/:projectId/state lists the running dbProviderMigration operation with its progress (the environments move one after the other, live first):

FieldTypeDescription
operations[].progress.phasestring"copying", then "verifying" (the copy is checked), then "switching"
operations[].progress.envstringThe database moving now: "live" or "dev"
operations[].progress.copiedBytesnumberBytes copied so far, updated after each copied part (a few hundred MB)
operations[].progress.totalBytesnumberThe database's size
operations[].progress.etaSecnumberAbout how many seconds are left for this database, from the speed measured so far

Meanwhile the agent and code changes are refused with 409 OPERATION_IN_PROGRESS and the live site is read-only: its pages keep answering and every write gets a short maintenance answer (503). When the move ends, the project's chat says so and the owner gets an e-mail: moved, or rolled back with the 50 credits refunded (the operation then stays listed with status: "error" and errorMessage).

End of Move to Turso Back to top