Databases API

Manage PostgreSQL databases.

Manage PostgreSQL databases.

List Databases

GET /projects/{projectId}/databases
Authorization: Bearer <platform_token>

Response:

{
  "data": [
    {
      "id": "abc-123-456-789",
      "database_name": "main_db",
      "status": "active",
      "connection_string": "postgresql://volcano_client_11111111-1111-1111-1111-111111111111:vpg_abc123@database.volcano.dev:5432/myapp_main?sslmode=require&application_name=volcano_full_access",
      "region": "us-east-1",
      "pg_version": "18",
      "storage_bytes": 1610612736,
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-01T00:00:00Z"
    }
  ]
}

storage_bytes is the database's latest observed storage: its own on-disk size, plus what each of its branches has diverged from it, plus what its backups cost to hold. It is the figure your storage allowance is enforced against, and the stats endpoint breaks it down. A background pass records it, so it is absent until the database has been sampled and can trail the stats endpoint's current_storage_bytes, which measures on request.

List, count and project-lookup failures return 500 with a fixed message, such as:

{ "error": "failed to list databases" }

Internal connection details are omitted. Include the response's X-Request-Id header when contacting support. Invalid or mismatched pagination cursors return 400.

Get Database

GET /projects/{projectId}/databases/{databaseName}
Authorization: Bearer <platform_token>

Response:

{
  "id": "abc-123-456-789",
  "database_name": "main_db",
  "status": "active",
  "connection_string": "postgresql://volcano_client_11111111-1111-1111-1111-111111111111:vpg_abc123@database.volcano.dev:5432/myapp_main?sslmode=require&application_name=volcano_full_access",
  "region": "us-east-1",
  "pg_version": "18",
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-01T00:00:00Z"
}

Note: connection_string is only shown when status is active. Use this connection string to connect from your functions or applications. It contains Volcano-managed per-database client credentials (volcano_client_{database_id} with a vpg_ password); internal credentials are never returned and will not authenticate through pgproxy.

Create Database

POST /projects/{projectId}/databases
Authorization: Bearer <platform_token>
Content-Type: application/json

Request:

{
  "name": "main_db"
}

Response: 201 Created

Database name is normalized to PostgreSQL format (lowercase, underscores). Names can be up to 64 characters.

Limit: Each project can hold 1 database on HOBBY and up to 10,000 on SUPERAGENT. Creating a database over the plan's cap returns 403 Forbidden.

Delete Database

DELETE /projects/{projectId}/databases/{databaseName}
Authorization: Bearer <platform_token>

Response: 204 No Content

Permanently deletes the database. Cannot be undone.

Returns 409 while a restore is running on the database; wait for it to finish. If Volcano cannot tell whether one is running it returns 503 rather than delete a database that might be mid-restore — retry.

Update Database Size

PATCH /projects/{projectId}/databases/{databaseName}/type
Authorization: Bearer <platform_token>
Content-Type: application/json
{
  "database_type": "volcano-db-s"
}

Returns the updated database. A size change may briefly interrupt active connections.

StatusMeaning
400Invalid request or database type.
401Authentication required.
403Access denied, or the requested size exceeds the plan allowance (code plan_required).
404Project or database not found.
409Database is not active or a restore is running. Wait until it is active and any restore finishes.
500Internal server error.
503Volcano cannot check whether a restore is running. Retry.

Errors use the shared error response format.

Reset Password

POST /projects/{projectId}/databases/{databaseName}/reset-password
Authorization: Bearer <platform_token>

Response:

{
  "message": "Password reset successful",
  "role_name": "volcano_client_11111111-1111-1111-1111-111111111111",
  "new_password": "vpg_new_secure_password",
  "connection_string": "postgresql://volcano_client_11111111-1111-1111-1111-111111111111:vpg_new_secure_password@database.volcano.dev:5432/mydb?sslmode=require&application_name=volcano_full_access"
}

Update your DATABASE_URL environment variable with the new connection string. The previous Volcano password stops opening new connections within a few seconds of the reset, and connections already open are not interrupted. Reset does not expose or rotate the internal owner password.

Returns 409 while a restore is running on the database — a restore rewrites the credentials on its way out — and 503 if Volcano cannot tell whether one is.

Branches

A branch is a copy-on-write fork of a database. See Branching for the concepts; this section is the endpoint reference.

List Branches

GET /projects/{projectId}/databases/{databaseName}/branches
Authorization: Bearer <platform_token>

Response:

{
  "data": [
    {
      "id": "9b1f0f4c-2b8e-4f43-9a71-1f6c2f0f2c31",
      "database_id": "abc-123-456-789",
      "project_id": "11111111-1111-1111-1111-111111111111",
      "name": "feature_checkout",
      "status": "active",
      "ttl_seconds": 86400,
      "expires_at": "2024-01-02T00:00:00Z",
      "storage_bytes": 536870912,
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-01T00:00:05Z"
    }
  ]
}

Every branch is listed, including those still provisioning and those that failed, since each still holds a name. Connection strings are omitted — fetch a single branch to get its connection string.

Create Branch

POST /projects/{projectId}/databases/{databaseName}/branches
Authorization: Bearer <platform_token>
Content-Type: application/json

Request:

{
  "name": "feature_checkout",
  "ttl_seconds": 86400
}

Response: 202 Accepted, with the branch in provisioning and no connection string. Poll Get Branch until it reports active.

ttl_seconds is between 3600 and 2592000 (one hour to 30 days) and defaults to seven days. Names are lowercase letters, numbers, and underscores, up to 64 characters, unique within the parent database. Names beginning with migration_rehearsal_ are reserved for the branches migration runs rehearse on.

StatusMeaning
400Invalid name or lifetime
403Branch allowance reached, or branching is not on this plan
404Project or database not found
409Name already exists, or the database cannot be branched right now
503Branching is temporarily unavailable

Limit: 10 branches per database on HOBBY, 25 on SUPERAGENT. Branches in every state count, including those still provisioning.

Get Branch

GET /projects/{projectId}/databases/{databaseName}/branches/{branchName}
Authorization: Bearer <platform_token>

Response:

{
  "id": "9b1f0f4c-2b8e-4f43-9a71-1f6c2f0f2c31",
  "database_id": "abc-123-456-789",
  "project_id": "11111111-1111-1111-1111-111111111111",
  "name": "feature_checkout",
  "status": "active",
  "connection_string": "postgresql://volcano_client_9b1f0f4c-2b8e-4f43-9a71-1f6c2f0f2c31:vpg_xyz789@database.volcano.dev:5432/main_db?sslmode=require&application_name=volcano_full_access",
  "ttl_seconds": 86400,
  "expires_at": "2024-01-02T00:00:00Z",
  "storage_bytes": 536870912,
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-01T00:00:05Z"
}

connection_string is present only while the branch is active. It carries the branch's own username and password; application_name selects the access mode exactly as it does for the parent. storage_bytes is the branch's divergence from its parent, not its apparent size.

Extend Branch

PATCH /projects/{projectId}/databases/{databaseName}/branches/{branchName}
Authorization: Bearer <platform_token>
Content-Type: application/json

Request:

{
  "ttl_seconds": 604800
}

Replaces the branch's lifetime and restarts the countdown from now. The new duration is remembered, so a later reset re-arms the same lifetime.

Reset Branch

POST /projects/{projectId}/databases/{databaseName}/branches/{branchName}/reset
Authorization: Bearer <platform_token>

Discards everything written on the branch and re-forks it from the parent as it is now. The branch keeps its name and connection string, and its lifetime is re-armed. It does not serve connections for the duration of the reset.

Returns 202 with the branch in provisioning; the rewind runs in the background. Poll the branch until it reports active before connecting again.

Returns 409 if the branch is not active, a reset is already in progress, or the parent database is being restored — a reset re-forks from the parent, so it waits for the restore and for the provider's cooldown afterwards.

Rotate Branch Password

POST /projects/{projectId}/databases/{databaseName}/branches/{branchName}/reset-password
Authorization: Bearer <platform_token>

Issues a new password and invalidates the previous connection string. Existing connections are not interrupted, and the previous password stops opening new ones within a few seconds. The parent database's credentials are untouched.

Delete Branch

DELETE /projects/{projectId}/databases/{databaseName}/branches/{branchName}
Authorization: Bearer <platform_token>

Response: 202 Accepted

{
  "status": "deleting",
  "message": "branch deletion in progress"
}

The branch stops accepting connections at once; its fork and its row are removed by a background job. Deleting a branch that is still provisioning is allowed and stops the build, and repeating the call while teardown is in progress is accepted again. Once the branch is gone the call returns 404.

Query a Branch

Every /query/* verb has a branch-targeted twin:

POST /databases/{databaseName}/branches/{branchName}/query/select
Authorization: Bearer <service_key or auth_user_access_token>
Content-Type: application/json

The request and response bodies are identical to the parent routes documented under Database Query API. The same tokens work — service keys and end-user tokens are project-scoped — and anonymous keys are rejected on a branch just as they are on a parent. A branch that does not exist answers 404, indistinguishable from a database that does not exist.

Backups

A backup is a copy of the database at a point in time. See Backups and restore for the concepts; this section is the endpoint reference. Backups cover the database itself — branches are neither backed up nor restored.

Every endpoint in this section is SUPERAGENT-only. On the HOBBY plan they all answer 403, reads included.

List Backups

GET /projects/{projectId}/databases/{databaseName}/backups
Authorization: Bearer <platform_token>

Response:

{
  "data": [
    {
      "name": "before_migration",
      "source": "manual",
      "size_bytes": 41943040,
      "expires_at": "2024-01-31T00:00:00Z",
      "created_at": "2024-01-01T00:00:00Z"
    }
  ],
  "restore_window": {
    "earliest_restore_at": "2023-12-25T00:00:00Z",
    "latest_restore_at": "2024-01-01T00:00:00Z"
  }
}

Newest first. source is manual, scheduled, or migration (taken before migrations were applied); only manual backups count against the plan's allowance. size_bytes is absent until the storage provider has costed the backup. restore_window is the span a point-in-time restore may target, and is absent on a plan without point-in-time restore. This route keeps working while a restore is running.

A database with no storage behind it yet — typically one still provisioning — answers 409 on the backup and schedule reads. Every other status reads fine, including a database being restored.

Create Backup

POST /projects/{projectId}/databases/{databaseName}/backups
Authorization: Bearer <platform_token>
Content-Type: application/json

Request:

{
  "name": "before_migration"
}

Response: 201 Created with the backup. Backups are taken synchronously — there is nothing to poll.

Names are lowercase letters, numbers, underscores, and hyphens, up to 63 characters, unique within the database. Names beginning with volcano- are reserved for the platform's own snapshots, and names beginning with before-migration- for migration backups.

StatusMeaning
400Invalid backup name, or one with a reserved prefix
403Backup allowance reached, or backups are SUPERAGENT-only
404Project or database not found
409Name already exists, a backup was taken less than a minute ago, or the database is restoring
503Backups are temporarily unavailable

Limit: 50 backups per database on SUPERAGENT, counting only backups you took. HOBBY has none — every endpoint in this section answers 403.

Get Backup

GET /projects/{projectId}/databases/{databaseName}/backups/{backupName}
Authorization: Bearer <platform_token>

Returns one backup. A name that does not exist answers 404.

Delete Backup

DELETE /projects/{projectId}/databases/{databaseName}/backups/{backupName}
Authorization: Bearer <platform_token>

Response:

{
  "status": "deleted",
  "message": "backup deleted"
}

Frees the backup's storage. Scheduled backups can be deleted too. A backup that is already gone answers 404, and a database being restored answers 409: the restore may still need the backup it is pinned to.

Get Backup Schedule

GET /projects/{projectId}/databases/{databaseName}/backup-schedule
Authorization: Bearer <platform_token>

Response:

{
  "entries": [{ "frequency": "daily", "hour": 3, "retention_seconds": 2592000 }]
}

An empty entries list means no automated backups.

Replace Backup Schedule

PUT /projects/{projectId}/databases/{databaseName}/backup-schedule
Authorization: Bearer <platform_token>
Content-Type: application/json

Request:

{
  "entries": [
    { "frequency": "daily", "hour": 3 },
    { "frequency": "weekly", "hour": 4, "day": 6, "retention_seconds": 1209600 }
  ]
}

Replaces the schedule wholesale; an empty list stops automated backups. The response is the stored schedule, including the retention that was applied.

frequency is daily, weekly, or monthly. hour is UTC (0-23). day is the day of the week (1-7, Monday to Sunday) for a weekly entry or the day of the month (1-28) for a monthly one; it is required for both and ignored for a daily entry. retention_seconds defaults to the plan's retention and is clamped to it.

Scheduled backups do not count against the plan's backup allowance.

StatusMeaning
400Invalid frequency, hour, or day
403Backups are SUPERAGENT-only
409The database is not active

List Restores

GET /projects/{projectId}/databases/{databaseName}/restores
Authorization: Bearer <platform_token>

Returns the database's restore history, newest first, capped at the 50 most recent with no pagination. Readable during a restore.

Restore Database

POST /projects/{projectId}/databases/{databaseName}/restores
Authorization: Bearer <platform_token>
Content-Type: application/json

Request: exactly one of backup_name or restore_to.

{
  "backup_name": "before_migration"
}
{
  "restore_to": "2024-01-01T09:30:00Z"
}

Response: 202 Accepted

{
  "id": "7f3a1c2e-5d4b-4a91-8c6f-2b1e9d0a4c73",
  "database_id": "abc-123-456-789",
  "project_id": "11111111-1111-1111-1111-111111111111",
  "kind": "snapshot",
  "status": "pending",
  "backup_name": "before_migration",
  "created_at": "2024-01-01T12:00:00Z",
  "updated_at": "2024-01-01T12:00:00Z"
}

The restore runs in the background with the database in restoring and not accepting connections. Poll Get Restore until it reports completed. Restores are in place and destructive: data written after the restored point is discarded, and the connection string is unchanged throughout.

restore_to must fall inside the restore_window from List Backups.

StatusMeaning
400Both targets or neither, or a time outside the window
403Backups and point-in-time restore are SUPERAGENT-only (code plan_required), or the plan includes restore history the database has not built up yet (no code)
404Project, database, or backup not found
409A restore is already in progress, the database is not active, another database operation is running, or too many pre-restore states are still held open
503Restores are temporarily unavailable

A 409 with code migration_run_in_progress means a migration run is backing up or applying to the database; run_id names it. See What a restore refuses.

Get Restore

GET /projects/{projectId}/databases/{databaseName}/restores/{restoreId}
Authorization: Bearer <platform_token>

Response:

{
  "id": "7f3a1c2e-5d4b-4a91-8c6f-2b1e9d0a4c73",
  "database_id": "abc-123-456-789",
  "project_id": "11111111-1111-1111-1111-111111111111",
  "kind": "point_in_time",
  "status": "completed",
  "restore_to": "2024-01-01T09:30:00Z",
  "completed_at": "2024-01-01T12:03:20Z",
  "created_at": "2024-01-01T12:00:00Z",
  "updated_at": "2024-01-01T12:03:20Z"
}

kind is snapshot or point_in_time, and the restore carries whichever of backup_name and restore_to it was started with. status is pending, running, completed, failed, or exhausted; an attempt that fails with tries left goes back to pending, while failed and exhausted both mean Volcano gave up. Either leaves the database failed if its data may already have been replaced, and active if the restore never started — a backup that is gone from the provider ends the restore without touching the database. error carries why the most recent attempt failed.

Migrations

A migration run applies SQL files to a database and records each one in the database's history, so a file runs once. See Database migrations for what a run does; this section is the endpoint reference.

Starting a run and recording a baseline need the databases.migrations capability. Where it is unavailable, they answer 404 with code feature_unavailable; reading the history and runs works either way. A read_only project access token can read; starting a run or a baseline needs a full one.

Start a Migration Run

POST /projects/{projectId}/databases/{databaseName}/migration-runs
Authorization: Bearer <platform_token>
Content-Type: application/json
Idempotency-Key: deploy-4f2a9c1

Request:

{
  "files": [
    {
      "name": "001_create_notes.sql",
      "sql": "CREATE TABLE notes (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), title text NOT NULL);"
    },
    {
      "name": "002_add_notes_pinned.sql",
      "sql": "ALTER TABLE notes ADD COLUMN pinned boolean NOT NULL DEFAULT false;"
    }
  ]
}

Response: 202 Accepted with the run, queued. Poll it until status is succeeded or failed.

Idempotency-Key is optional, up to 255 printable ASCII characters. A repeat with a key the database has seen returns that run with 200 and starts nothing; the same key with different files answers 409.

StatusMeaning
400Over 1,000 files, a file over 1 MiB, a name that is not a .sql file, a name repeated (code invalid_file), a directory other than this database's (code invalid_layout), or a database over its storage allowance (no code; the message starts storage limit reached)
404Project or database not found, or migration runs are unavailable (code feature_unavailable)
409A run is already queued or running (code migration_run_in_progress, with its run_id), or the Idempotency-Key started a run of other files
413The request body is over 4 MiB

Get a Migration Run

GET /projects/{projectId}/databases/{databaseName}/migration-runs/{runId}
Authorization: Bearer <platform_token>

Response:

{
  "id": "7f3a1c2e-5d4b-4a91-8c6f-2b1e9d0a4c73",
  "database": {
    "type": "database",
    "id": "0b9c3f5e-2a41-4d7e-9f0a-6c8e1d2b3a45",
    "name": "main_db"
  },
  "status": "failed",
  "phase": "rehearse",
  "source": "api",
  "files": [
    { "name": "001_create_notes.sql", "outcome": "already_applied" },
    {
      "name": "002_add_notes_pinned.sql",
      "outcome": "failed",
      "error": {
        "message": "column \"pinned\" of relation \"notes\" already exists",
        "sqlstate": "42701",
        "line": 1
      }
    }
  ],
  "warnings": [],
  "error": {
    "code": "migration_failed",
    "message": "migration 002_add_notes_pinned.sql failed at line 1 (SQLSTATE 42701); it failed in rehearsal, so production was not changed"
  },
  "started_at": "2024-01-01T00:00:02Z",
  "completed_at": "2024-01-01T00:01:10Z",
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-01T00:01:10Z"
}

phase is the step the run is on, or ended on: plan, rehearse, backup, or apply. source is api for a run started here, or git_deploy for one a push started with apply_migrations on; a git_deploy run carries the pushed commit_sha. backup_name names the run's backup once the run starts taking it; a run that ended in its backup phase may name one that was never created. Each file's outcome is pending, already_applied, applied, failed, not_run, or unknown. A failed file's own SQL failed, and its transaction rolled back. not_run covers files after one that failed or lost its COMMIT, and a file rolled back by a lost connection or by Volcano's own statements around it. A file another session applied or baselined after the run planned ends already_applied. unknown means the run ended in apply before it confirmed whether the file ran, for example because it used every attempt or the file's COMMIT failed or was cut short. The file may have committed; the database's history says, and the next run applies the file only if the history does not record it. Each attempt reads the history again before it runs a file, so while a run retries, the files it has not applied read pending again, or already_applied if the history now holds them. A failed file's error.message is the database's own message and can quote values from your file or data. Volcano keeps at most 8 KiB of it; a longer message is cut and ends with … [truncated]. Volcano reads each reply the database sends about a file up to 4 MiB, which leaves room for a reply that quotes the whole file twice, as Postgres does for a statement the file executes. A larger reply is not read: the file fails, and its error.message says so instead. A run keeps at most 100 warnings; one that raised more ends the list with a warnings_truncated warning whose message says how many more were not kept. See Warnings.

List Migration Runs

GET /projects/{projectId}/databases/{databaseName}/migration-runs
Authorization: Bearer <platform_token>

Returns the 50 most recent runs, newest first, in data, each without files. Get a run for its files.

Get Migration History

GET /projects/{projectId}/databases/{databaseName}/migrations
Authorization: Bearer <platform_token>

Response:

{
  "history": [
    {
      "name": "001_create_notes.sql",
      "checksum": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
      "status": "applied",
      "applied_at": "2024-01-01T00:00:40Z",
      "run_id": "5c2d8e1a-3b4f-4c6d-9e7a-1f2b3c4d5e6f"
    }
  ],
  "active_run_id": "7f3a1c2e-5d4b-4a91-8c6f-2b1e9d0a4c73"
}

Every file the database's history records, in name order. status is applied, or baselined for a file recorded without running. active_run_id is the run that is queued or running, if any. Volcano reads at most 1,000 entries; Read the history says how to bring a larger history back under the bound.

StatusMeaning
404Project or database not found
409The history table is gone from a database where migrations ran (code history_missing; see Recover a lost history), the history has more than 1,000 entries (code history_too_large), or the database is not active
503Volcano could not reach the database; retry

Record a Baseline

POST /projects/{projectId}/databases/{databaseName}/migrations/baseline
Authorization: Bearer <platform_token>
Content-Type: application/json

Takes the same body as starting a run. Records each file as applied without running it. After history_missing, a baseline of exactly the files that were already applied rebuilds the history. No run starts while a baseline records, and a baseline does not queue: while another request is changing the database, or the database holds the baseline up, such as with a lock on its history, it answers 409 with code database_busy.

Response:

{
  "recorded": ["001_create_notes.sql"]
}

recorded is empty when the history already held every file.

StatusMeaning
400The same file rules as starting a run, or a database over its storage allowance
404Project or database not found, or migration runs are unavailable (code feature_unavailable)
409A file is recorded with a different checksum (code checksum_mismatch), a run is queued or running (code migration_run_in_progress), the history has more than 1,000 entries (code history_too_large), the database is busy (code database_busy; try again), or the database is not active or changed while the request ran
413The request body is over 4 MiB
503Volcano could not reach the database; retry

Database Query API

Query databases directly via REST API - no SQL required!

SELECT - Query Data

POST /databases/{databaseName}/query/select
Authorization: Bearer <auth_user_access_token>
Content-Type: application/json

Request:

{
  "table": "posts",
  "select": ["id", "title", "content"],
  "filters": [
    { "column": "status", "operator": "eq", "value": "published" },
    { "column": "views", "operator": "gt", "value": 100 }
  ],
  "order": [{ "column": "created_at", "ascending": false }],
  "limit": 10,
  "offset": 0
}

Response:

{
  "data": [
    {
      "id": "uuid",
      "title": "My Post",
      "content": "Post content",
      "status": "published",
      "views": 150,
      "created_at": "2026-01-13T10:00:00Z"
    }
  ],
  "count": 1
}

Authentication: Requires auth user access token (from signup/signin), not platform token

Row-Level Security: Applied on tables with RLS enabled. A table without RLS returns every row. See Default grants

Filter Operators:

  • eq (equals)
  • neq (not equals)
  • gt (greater than)
  • gte (greater than or equal)
  • lt (less than)
  • lte (less than or equal)
  • like (pattern match, case-sensitive)
  • ilike (pattern match, case-insensitive)
  • is (NULL check)
  • in (array membership)

See Also: REST API Guide | Query Builder API

INSERT - Create Data

POST /databases/{databaseName}/query/insert
Authorization: Bearer <auth_user_access_token>
Content-Type: application/json

Request:

{
  "table": "posts",
  "values": {
    "title": "My New Post",
    "content": "Content here",
    "status": "draft"
  }
}

Response:

{
  "data": [
    {
      "id": "uuid",
      "title": "My New Post",
      "content": "Content here",
      "status": "draft",
      "user_id": "user-uuid",
      "created_at": "2026-01-13T10:00:00Z"
    }
  ],
  "count": 1
}

Note: user_id is automatically set to the authenticated user via trigger using auth.uid()

UPDATE - Modify Data

POST /databases/{databaseName}/query/update
Authorization: Bearer <auth_user_access_token>
Content-Type: application/json

Request:

{
  "table": "posts",
  "values": {
    "title": "Updated Title",
    "status": "published"
  },
  "filters": [{ "column": "id", "operator": "eq", "value": "post-uuid" }]
}

Response:

{
  "data": [
    {
      "id": "post-uuid",
      "title": "Updated Title",
      "status": "published",
      "updated_at": "2026-01-13T10:05:00Z"
    }
  ],
  "count": 1
}

Row-Level Security: If RLS blocks the update (not your data), returns empty array

DELETE - Remove Data

POST /databases/{databaseName}/query/delete
Authorization: Bearer <auth_user_access_token>
Content-Type: application/json

Request:

{
  "table": "posts",
  "filters": [{ "column": "id", "operator": "eq", "value": "post-uuid" }]
}

Response:

{
  "data": [
    {
      "id": "post-uuid",
      "title": "Deleted Post"
    }
  ],
  "count": 1
}

Safety: UPDATE and DELETE require at least one filter to prevent accidental mass changes

Row-Level Security: If RLS blocks the delete (not your data), returns empty array


Get Database Stats

GET /projects/{projectId}/databases/{databaseName}/stats
Authorization: Bearer <platform_token>

Query Parameters:

  • from - Start time (RFC3339, default: 24h ago)
  • to - End time (RFC3339, default: now)
  • granularity - hourly, daily, monthly (default: hourly)

Response:

{
  "current_storage_bytes": 1610612736,
  "current_storage_mb": 1536.0,
  "branches": [
    { "id": "9b1f0f4c-2b8e-4f43-9a71-1f6c2f0f2c31", "name": "feature_checkout", "storage_bytes": 536870912 }
  ],
  "backup_storage_bytes": 429496729,
  "storage_bytes": 12345678,
  "data_written_bytes": 9876543,
  "data_transfer_bytes": 5432109,
  "compute_time_seconds": 123.45,
  "active_time_seconds": 456.78,
  "time_range": "2024-01-01T00:00:00Z to 2024-01-02T00:00:00Z",
  "granularity": "hourly"
}

current_storage_bytes is the on-disk size right now: the database itself, plus every branch's divergence from it, plus what its backups cost to hold. This is the figure your storage allowance is enforced against. branches and backup_storage_bytes break it down, and branches is ordered most expensive first.

A branch is charged only for what it has written since the fork. A backup is not: one you take is charged as a full copy of the database as it was then, and a schedule's later snapshots only for what they add. backup_storage_bytes is sampled from the provider rather than measured live, so it can lag a change by a few minutes; it is zero on a plan without backups. See what backups cost.

storage_bytes is the historical synthetic storage figure from the consumption API and is unrelated.


Get Database Queries

GET /projects/{projectId}/databases/{databaseName}/queries
Authorization: Bearer <platform_token>

Returns the database's current top queries from pg_stat_statements, ranked by total execution time.

Only data statements (SELECT, INSERT, UPDATE, DELETE, MERGE) are listed, with literal values replaced by ?. Statements Volcano runs to operate the database are left out: Volcano runs them as the volcano_platform role, which is never listed.

role is the Postgres role that ran the query. Common values are anon and authenticated for RLS-enforced connections, service_role, and the database owner role for full-access connections such as the default connection string and service-key queries. Roles you create appear by name, and a role that no longer exists appears as unknown.

SUPERAGENT plan required.

Query Parameters:

  • limit - Maximum number of queries to return, from 1 to 100 (default: 10)

Response:

{
  "data": [
    {
      "query_id": "123456789",
      "query": "select * from posts where user_id = ?",
      "database": {
        "id": "11111111-1111-1111-1111-111111111111",
        "name": "app"
      },
      "role": "authenticated",
      "calls": 42,
      "total_exec_time_seconds": 1.25,
      "max_exec_time_seconds": 0.2,
      "mean_exec_time_seconds": 0.03,
      "min_exec_time_seconds": 0.01,
      "rows_processed": 420
    }
  ]
}

Database Status

  • provisioning - Being created (5-10 seconds)
  • active - Ready to use
  • restoring - Being restored; not connectable, and most operations on it return 409
  • failed - Creation failed, or a restore gave up after its data may have been replaced
  • deleting - Being torn down

See Also

On this page