Skip to content
runlot
ReferenceAPIDatabase

Database

Covers 14 API operations in the database category.

MethodPathDescription
GET/v1/orgs/{orgSlug}/projects/{projectName}/databaseWhether the database is available
POST/v1/orgs/{orgSlug}/projects/{projectName}/databaseCreates a new database after the previous one was deleted
GET/v1/orgs/{orgSlug}/projects/{projectName}/database/connectInformation needed to connect with psql or a driver
GET/v1/orgs/{orgSlug}/projects/{projectName}/database/endpointConnection info without credentials (runlot port-forward)
GET/v1/orgs/{orgSlug}/projects/{projectName}/database/tokensList of valid short-lived credentials
POST/v1/orgs/{orgSlug}/projects/{projectName}/database/tokensIssue a credential for short-lived automation (runlot pg token)
DELETE/v1/orgs/{orgSlug}/projects/{projectName}/database/tokens/{tokenUser}Revoke a short-lived credential
POST/v1/orgs/{orgSlug}/projects/{projectName}/database/executeRun one statement (the dashboard's SQL console)
POST/v1/orgs/{orgSlug}/projects/{projectName}/database/exportCreates a diagnostic image (runlot pg export)
GET/v1/orgs/{orgSlug}/projects/{projectName}/database/generationsList of backup generations (runlot pg generations)
POST/v1/orgs/{orgSlug}/projects/{projectName}/database/restoreRestore to a generation (runlot pg restore)
POST/v1/orgs/{orgSlug}/projects/{projectName}/database/deleteDelete a database (runlot pg delete)
GET/v1/orgs/{orgSlug}/projects/{projectName}/database/operations/{opId}The progress of one operation
POST/v1/orgs/{orgSlug}/projects/{projectName}/database/operations/{opId}/abortAborts an in-progress operation (runlot pg abort)

GET /v1/orgs/{orgSlug}/projects/{projectName}/database

The response is just {db: bool}. Size and generation are not reported here.

No credentials are returned. Viewer and above may call this; connection details live at /database/connect.

operationId getDatabase

Status codeDescriptionResponse body
200Whether the database is availableobject
403——
404——

POST /v1/orgs/{orgSlug}/projects/{projectName}/database

Every project has a database from the start; this creates a new, empty one after a delete, and clears the turn-off so later deploys keep it. It returns the connection details. Shortly afterwards the running worker picks up a connected env.db. No redeploy is needed.

It is idempotent and never rotates the password. A second call returns the same connection details, so a connection string saved from the first response keeps working.

operationId createDatabase

Status codeDescriptionResponse body
200Credentials and connection infoDatabaseConnect
403——
404——

GET /v1/orgs/{orgSlug}/projects/{projectName}/database/connect

Returns the host, port, database name, user, password and sslmode needed to connect with psql or any PostgreSQL driver. Requires member or higher — a viewer can see that a database exists, but never its password.

operationId getDatabaseConnect

Status codeDescriptionResponse body
200Connection infoDatabaseConnect
403——
404The project does not exist, or its database has not been created yetError

GET /v1/orgs/{orgSlug}/projects/{projectName}/database/endpoint

Only host, port, database and sslmode — no secret, so viewer and above may call it. runlot port-forward authenticates with your login session rather than a password, so this endpoint is all it needs.

operationId getDatabaseEndpoint

Status codeDescriptionResponse body
200Endpointobject
403——
404——

GET /v1/orgs/{orgSlug}/projects/{projectName}/database/tokens

Lists the project's short-lived database credentials. Passwords are never included. Requires member or higher.

operationId listDatabaseTokens

Status codeDescriptionResponse body
200Listobject
403——
404——

POST /v1/orgs/{orgSlug}/projects/{projectName}/database/tokens

Issues a user/password pair for use in a connection string. The password appears only in this response — only its SCRAM verifier is stored, so a lost password must be reissued. The session still opens under the project's own database role; the token user is only the login name. Requires member or higher.

operationId createDatabaseToken

Request body: application/json · object

Status codeDescriptionResponse body
201The issued credentialDatabaseToken
400——
403——
404——

DELETE /v1/orgs/{orgSlug}/projects/{projectName}/database/tokens/{tokenUser}

The next connection onward is rejected. Deleting a credential that does not exist returns 404. Available to member and above.

operationId deleteDatabaseToken

Status codeDescriptionResponse body
204Revoked—
403——
404——

POST /v1/orgs/{orgSlug}/projects/{projectName}/database/execute

Runs a single SQL statement against the project's database and returns the rows. This backs the dashboard's SQL console; from a terminal, use runlot pg execute, which connects directly instead.

The statement is executed over an ordinary PostgreSQL connection using the same host, port, sslmode and credential that …/database/connect hands a member for psql, so every rule that applies to a normal connection applies here too, including the 10 s statement ceiling.

readOnly defaults to true, and an absent field means true. Read-only mode runs inside BEGIN … READ ONLY and always rolls back; it never commits.

Values come back as strings or null, never JSON numbers, so large integer ids keep their exact digits. NULL is null and the empty string is "".

At most 500 rows are listed. The cap applies to the response only and is not added to your statement, so rowCount still reports what the statement actually did and truncated tells you the listing was cut.

Write mode belongs to a dashboard session. A personal access token reaches this route with db:read and runs read-only statements; asking for readOnly: false with a token is 403 console_write_needs_session. No scope opens it — a credential that has to write goes through …/database/connect or …/database/tokens and connects, where the power it carries is named on the token.

Requires member or higher. A write leaves a db.execute audit event; a read leaves none.

operationId executeSQL

Request body: application/json · ExecuteRequest

Status codeDescriptionResponse body
200The statement ranExecuteResult
400The database refused the statement. code is sql_error and details.sqlstate carries PostgreSQL's own five-character error code. details may also hold detail, hint and position.Error
403insufficient_scope — the token carries neither db:read nor a wider scope; or console_write_needs_session — readOnly: false from something other than a dashboard session.Error
404——
502could not reach this project's database (console_unreachable)—
504the database did not answer within the round-trip deadline (console_timeout)—

POST /v1/orgs/{orgSlug}/projects/{projectName}/database/export

This is not a backup. It creates an immutable diagnostic image of the database as it is right now. It does not move any backup pointer, does not affect retention, and cannot be selected by a restore.

Scheduled backups are separate and run on their own hourly schedule. List them at …/database/generations and pick one at …/database/restore. Diagnostic images stay outside that history entirely: restore cannot choose one and pruning ignores them.

This is not idempotent. Each call creates one more image.

Requires member or higher, because the image covers the whole database.

It is slow. The response arrives only once the dump has finished, and the time scales with the size of the database.

operationId exportDatabase

Status codeDescriptionResponse body
200The diagnostic key that was createdDatabaseExport
403——
404The project does not exist, or its database has not been created yetError
409The project is suspended (suspended). A diagnostic image can only be taken from a running project; to get data out of a suspended one, use restore instead.Error
502The image could not be built (export_failed)Error
503The project cannot be exported right now: either no process is running for it (no_home_node), or the service is temporarily unavailable (no_node_admin).Error

GET /v1/orgs/{orgSlug}/projects/{projectName}/database/generations

Lists the backup generations a restore can choose from, newest first, each with its storage key, size and sha256 digest so the restore can verify what it receives.

lastCheckedAt records when the backup was last checked. A generation that is not advancing and a backup that has stopped working are different things, and this value tells them apart. Empty means no check has run yet.

Requires member or higher.

The database does not have to be enabled. A final safety generation is kept even after runlot pg delete, so it still appears here after deletion.

objects is the Durable Object half of the same history, returned alongside the database generations because a project's restorable state is the union of the two.

The two halves are timed independently, so objects.atomic is always false. objects.restorable is objects.available && objects.complete; when it is false, a restore will refuse outright because a component is missing or a gap was found. restorable=true means only that the catalogue is eligible — a restore can still fail later, for example if a node is unreachable or a stored archive is lost or corrupt.

objects is selected at one instant for the whole project (default: now), not per generation. A restore targets one specific (epoch, seq) generation with its own lastAt. To find out whether the object half is eligible for that generation, call this endpoint again with ?at=<that generation's lastAt>; the two answers can differ.

operationId listGenerations

ParameterLocationRequiredTypeDescription
atqueryNostringNarrows the object half only, to the newest component at or before this instant. Database generations are always listed in full.
Status codeDescriptionResponse body
200List of generations, newest firstDatabaseGenerations
403——
404——

POST /v1/orgs/{orgSlug}/projects/{projectName}/database/restore

A restore stops the project's database, replaces its contents with the chosen generation, and starts it again. The data as it stands immediately before the restore is archived first, so there is a way back.

Any writes made after the chosen generation are lost. This requires admin or higher, and the CLI asks you to type the project name again.

If the project has Durable Objects, the same operation restores them too. Each affected object is archived and then set back to its selected component; objects placed after the restore target are removed. A missing object component refuses the whole restore, database included, rather than restoring only part of the project.

Check objects.restorable before calling this, for the generation you are about to pass. Call …/database/generations?at=<the chosen generation's lastAt> first: the object half is selected on its own instant, independent of the (epoch, seq) you send here, so a restorable=true read at another instant answers a different question. Even a correctly timed restorable=true is eligibility, not a guarantee — the restore can still fail once it starts.

The server never resolves latest. Pass the exact (epoch, seq) you saw in the list, so a generation created in the meantime cannot send you somewhere else.

Returns 202 with an operation id, not the result. Poll …/database/operations/{opId} for progress.

operationId restoreDatabase

Request body: application/json · RestoreRequest

Status codeDescriptionResponse body
202Opened an operationOperationStarted
400——
403——
404The project does not exist, or its database has not been created yetError
409Another operation is already in progress (operation_in_progress), or the project is suspended (suspended).Error
503The project cannot be restored right now (for example, no capacity is available)Error

POST /v1/orgs/{orgSlug}/projects/{projectName}/database/delete

Deletes the project's database. A final safety generation is kept, so the data can still be listed and restored afterwards. Once the deletion completes, the worker runs without env.db. Requires admin or higher.

This also turns end-user auth off. The subscriber records live inside the project's database, so they go with it, and env.auth is disabled in the same step. The stored OAuth provider secrets are removed with it and are not recoverable.

confirm must match the project name exactly. If it does not, the call returns 400 confirm_mismatch and nothing starts.

This is a POST rather than a DELETE because it is a long-running operation that returns 202 and takes minutes, and because it carries a confirmation string in the request body.

operationId deleteDatabase

Request body: application/json · DeleteRequest

Status codeDescriptionResponse body
202Opened an operationOperationStarted
400The confirmation string does not match the project name (confirm_mismatch)Error
403——
404The project does not exist, or its database has not been created yetError
409Another operation is already in progress (operation_in_progress)Error

GET /v1/orgs/{orgSlug}/projects/{projectName}/database/operations/{opId}

Watches the progress of a restore or delete. The CLI polls every 2 seconds and prints whenever the phase changes.

The operation must belong to the project named in the path. An id from another project answers 404, as if it did not exist.

Requires member or higher.

operationId getDatabaseOperation

Status codeDescriptionResponse body
200Operation statusOperation
403——
404The project has no such operation (operations belonging to other projects also land here)Error

POST /v1/orgs/{orgSlug}/projects/{projectName}/database/operations/{opId}/abort

Only possible before the operation reaches a point of no return: evict, restore_generation and delete can be aborted up to the committing phase; restore cannot. Aborting returns the project to active and leaves terminalCode=aborted.

Requires admin or higher, and the operation must belong to the project in the path.

operationId abortDatabaseOperation

Status codeDescriptionResponse body
200Status of a suspended operationOperation
403——
404The project has no such operationError
409Past the point of no return (not_abortable) — either after committing or a restore operation.Error

On this page