For AI agents: markdown of this page — /docs-content-en/infra/database/execute.md documentation index — /llms.txt
Run write SQL
POST /v1/infra/servers/:id/db/execute
Runs write SQL statements against the application's SQLite database in one transaction: changes rows, creates and alters tables, indexes and views.
Before each call, the platform saves a separate protective backup of the database — see Protective backups before writes. Access conditions — When the database is available.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
Parameterid (path) |
Typestring (UUID) | Requiredyes | DescriptionID of an application with kind equal to GALAXY_APP. List: GET /v1/infra/servers |
Request fields (body)
| Field | Type | Required | Description |
|---|---|---|---|
Fieldfile |
Typestring | Requiredno | DescriptionDatabase file path relative to the /data volume, up to 1024 bytes in UTF-8. Absolute paths and . or .. segments are not accepted. Defaults to the VIBE_DB_PATH value of the application container, or app.db if it is not set. File list: GET /db |
Fieldsql |
Typestring | Requiredyes | DescriptionFrom 1 to 16384 bytes in UTF-8. Up to 64 statements separated by ;. Statements run in order |
Fieldparameters |
Typearray | Requiredno | DescriptionValues for ? placeholders, up to 256 items. An item is null, a string up to 16384 bytes in UTF-8 or a finite number. An integer must not exceed 2⁵³−1 in absolute value; pass an exact 64-bit integer as a literal in sql. Booleans are not accepted — pass 1 or 0. Allowed only when sql contains one statement. Default: [] |
An unknown body field is rejected with 400 VALIDATION_ERROR.
The following in sql are rejected with 422 APP_DB_SQL_ERROR and reason not_permitted: transaction and savepoint control (BEGIN, COMMIT, ROLLBACK, SAVEPOINT), ATTACH and DETACH, PRAGMA, creating and dropping virtual tables, access to schemas other than main, including temporary tables, and the load_extension, readfile, writefile and fts3_tokenizer functions. VACUUM does not run either: the call runs inside a transaction, and the statement is rejected with 422 APP_DB_SQL_ERROR.
Examples
curl — personal key
curl -X POST "https://vibecode.bitrix24.com/v1/infra/servers/APP_ID/db/execute" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"sql":"UPDATE notes SET body = ? WHERE id = ?","parameters":["updated",1]}'
curl — OAuth application
curl -X POST "https://vibecode.bitrix24.com/v1/infra/servers/APP_ID/db/execute" \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"sql":"UPDATE notes SET body = ? WHERE id = ?","parameters":["updated",1]}'
JavaScript — personal key
const url = `https://vibecode.bitrix24.com/v1/infra/servers/${appId}/db/execute`
const headers = { 'X-Api-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }
const body = JSON.stringify({ sql: 'UPDATE notes SET body = ? WHERE id = ?', parameters: ['updated', 1] })
// Only 409 APP_DB_BACKUP_NOT_READY is retried: with this response the SQL has not run yet
const deadline = Date.now() + 180_000
let result
while (Date.now() < deadline) {
const res = await fetch(url, { method: 'POST', headers, body })
result = await res.json()
if (res.status !== 409 || result.error?.code !== 'APP_DB_BACKUP_NOT_READY') break
const seconds = Number(res.headers.get('Retry-After')) || 1
await new Promise(resolve => setTimeout(resolve, Math.max(seconds, 3) * 1000))
}
if (!result.success) throw new Error(result.error.code)
console.log(result.data.rowCount, result.data.backupId)
JavaScript — OAuth application
const url = `https://vibecode.bitrix24.com/v1/infra/servers/${appId}/db/execute`
const headers = {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
'Content-Type': 'application/json',
}
const body = JSON.stringify({ sql: 'UPDATE notes SET body = ? WHERE id = ?', parameters: ['updated', 1] })
const deadline = Date.now() + 180_000
let result
while (Date.now() < deadline) {
const res = await fetch(url, { method: 'POST', headers, body })
result = await res.json()
if (res.status !== 409 || result.error?.code !== 'APP_DB_BACKUP_NOT_READY') break
const seconds = Number(res.headers.get('Retry-After')) || 1
await new Promise(resolve => setTimeout(resolve, Math.max(seconds, 3) * 1000))
}
if (!result.success) throw new Error(result.error.code)
Response fields
| Field | Type | Description |
|---|---|---|
Fieldsuccess |
Typeboolean | Descriptiontrue if the transaction was committed |
Fielddata.file |
Typestring | DescriptionDatabase file relative to the /data volume that the SQL was applied to |
Fielddata.rowCount |
Typeinteger | DescriptionNumber of rows changed directly by INSERT, UPDATE and DELETE statements. Changes made by triggers are not counted. A statement that changes the database schema, for example CREATE TABLE … AS SELECT, is not counted |
Fielddata.statementCount |
Typeinteger | DescriptionNumber of statements run, from 1 to 64 |
Fielddata.durationMs |
Typenumber | DescriptionSQL execution time on the host in milliseconds, with a fractional part |
Fielddata.backupId |
Typestring | DescriptionID of the protective backup saved before this call. Backup list: GET /db/backups |
Response example
{
"success": true,
"data": {
"file": "app.db",
"rowCount": 1,
"durationMs": 0.933,
"statementCount": 1,
"backupId": "d5e1b7a9-0f42-4c8d-9a3e-6b21c4f80e57"
}
}
Error response example
422 — the PRAGMA journal_mode=DELETE statement is not permitted; the transaction was rolled back:
{
"success": false,
"error": {
"code": "APP_DB_SQL_ERROR",
"message": "SQL operation is not permitted",
"details": {
"reason": "not_permitted"
}
}
}
Errors
| HTTP | Code | Description |
|---|---|---|
| HTTP400 | CodeVALIDATION_ERROR |
DescriptionThe body failed validation: an unknown field, sql empty or longer than 16384 bytes, an invalid file, more than 256 items or a boolean in parameters |
| HTTP400 | CodeAPP_DB_INVALID_FILE |
DescriptionThe file is not an SQLite database, its path goes through a symbolic link, or the container's VIBE_DB_PATH value does not point inside /data |
| HTTP400 | CodeAPP_DB_UNSUPPORTED_PLACEMENT |
DescriptionThe application is not hosted in Galaxy |
| HTTP401 | CodeMISSING_API_KEY |
DescriptionThe X-Api-Key header is missing |
| HTTP401 | CodeINVALID_API_KEY |
DescriptionThe key is not recognized |
| HTTP402 | CodeBILLING_FROZEN |
DescriptionThe portal's billing is frozen |
| HTTP402 | CodeBILLING_INSUFFICIENT |
DescriptionThe portal balance is insufficient to store the protective backup |
| HTTP403 | CodeINFRA_SCOPE_REQUIRED |
DescriptionThe key lacks the vibe:infra scope |
| HTTP403 | CodeAPP_DB_DISABLED |
DescriptionApplication database access is not enabled for the portal |
| HTTP403 | CodeAPP_DB_READONLY_KEY |
DescriptionThe key is read-only |
| HTTP404 | CodeNOT_FOUND |
DescriptionThe application was not found, was deleted or is not accessible to this key |
| HTTP404 | CodeAPP_DB_NOT_FOUND |
DescriptionThe application has no /data volume or database file: the application has not created it yet |
| HTTP409 | CodeAPP_DB_BACKUP_NOT_READY |
DescriptionThe protective backup is still being prepared; the SQL did not run. Retry the request after the pause from the Retry-After header |
| HTTP409 | CodeAPP_DB_BUSY |
DescriptionA deploy, migration or restore of the application is in progress, the host is busy with another operation, the database is busy with a write by the application itself, or waking the application or galaxy host is blocked. The SQL did not run. Retry after the pause from Retry-After |
| HTTP409 | CodeAPP_DB_WAL_CLOSED |
DescriptionThe protective backup was not created: a running application has closed its database connection in write-ahead logging (WAL) mode. The SQL was not sent; there is no Retry-After header. Configure the application to keep the connection open |
| HTTP413 | CodePAYLOAD_TOO_LARGE |
DescriptionThe body exceeds 64 KiB, or the full input of the service process on the host exceeds 96 KiB |
| HTTP422 | CodeAPP_DB_SQL_ERROR |
DescriptionThe SQL did not run; the transaction was rolled back. The cause is in details.reason: syntax_error, not_permitted, no_such_table, no_such_column, type_mismatch, constraint_failed or other. Reason other is also returned for parameters with several statements and for more than 64 statements |
| HTTP422 | CodeAPP_DB_QUERY_TIMEOUT |
DescriptionExecution took longer than 30 seconds; the transaction was rolled back |
| HTTP422 | CodeAPP_DB_CORRUPT |
DescriptionThe database integrity check before the write failed, or the protective backup was not created because the database is corrupted |
| HTTP422 | CodeAPP_DB_BACKUP_TIMEOUT |
DescriptionThe protective backup was not created within the allotted time; the SQL was not sent |
| HTTP422 | CodeAPP_DB_BACKUP_INVALID |
DescriptionThe protective backup failed validation; the SQL was not sent |
| HTTP429 | CodeRATE_LIMITED |
DescriptionThe write request limit for the portal was exceeded — Limits. The pause is in the Retry-After header |
| HTTP503 | CodeAPP_DB_PRE_CHANGE_FAILED |
DescriptionThe protective backup was not created for another reason; the SQL was not sent |
| HTTP503 | CodeAPP_DB_WRITE_OUTCOME_UNKNOWN |
DescriptionThe SQL was sent, but no result was received; details.retryable is false. The change may have been saved. There is no idempotency key, so do not retry the request automatically: first read the data through POST /db/query |
| HTTP503 | CodeAPP_DB_TOOL_UNAVAILABLE |
DescriptionThe host is not prepared or the service channel to it is unavailable. Host readiness — GET /app-db/prepare |
| HTTP503 | Codeuser_self_deletion_pending |
DescriptionThe key owner is deleting their account on the portal |
Full list of common API errors — Errors.
Known specifics
- The first call only prepares the protective backup. While there is no fresh backup for this file, the call queues its creation and returns
409 APP_DB_BACKUP_NOT_READYwithdetails.backupId,details.retryAfterSecondsand aRetry-Afterheader. Repeat the same request: once the backup is ready, the retry runs the SQL and returns the same ID indata.backupId. A ready backup waits 10 minutes for the retry; after that, the next call prepares a new one. How protective backups work — Protective backups before writes. - Retries consume the write limit. Each retry after
409counts toward the write request limit (Limits), so pause for at least three seconds, even whenRetry-Afteris1. - A backup failure is reported once. If the protective backup was not created, the next call receives the cause with
details.retryable: false, and the call after that queues a new backup. - The whole call is one transaction. An error in any statement or exceeding 30 seconds rolls back all statements of the call. Rows returned by
SELECTandRETURNINGare read to the end and discarded: they are not included in the response. SQL and parameter values are not written to the audit log. - Foreign keys are always enforced. For the duration of the call, the platform enables foreign key checks, even if the application itself does not. A referential integrity violation rolls back the call with reason
constraint_failed. - The application container is not started. The SQL is run by a service container on the host with the same
/datavolume. - The AI agent tool retries only while the backup is not ready. The
manage_app_dbtool of the Vibecode MCP server withaction: "execute"acceptsserverId,file,sql,parametersand retries only409 APP_DB_BACKUP_NOT_READYon its own. The total wait is 180 seconds; the pause followsRetry-After, but is no shorter than three seconds. The tool does not retry a network error orAPP_DB_WRITE_OUTCOME_UNKNOWN. Cancelling the wait does not prove that an already sent write was rolled back.