For AI agents: markdown of this page — /docs-content-en/infra/database/query.md documentation index — /llms.txt
Run a read-only SQL query
POST /v1/infra/servers/:id/db/query
Runs one read-only SQL query against the application's SQLite database and returns the result columns and rows. Use it for queries with conditions, joins and aggregates that reading the rows of a single table does not support.
Database access conditions and request limits — When the database is available and Limits. To change data — Run write SQL.
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 |
|---|---|---|---|
Fieldsql |
Typestring | Requiredyes | DescriptionOne read-only SQL statement, up to 16,384 bytes in UTF-8. Values are bound through positional ? placeholders |
Fieldparameters |
Typearray | Requiredno | DescriptionValues for the ? placeholders in order, up to 256 items. Each is a string up to 16,384 characters, a finite number or null. Booleans are not accepted; pass 1 and 0 instead. Default: [] |
Fieldfile |
Typestring | Requiredno | DescriptionDatabase file path relative to /data, up to 1024 bytes in UTF-8. File list — data.files[].file in GET /db. Default — data.defaultFile from the same response |
Other body fields return 400 VALIDATION_ERROR. The body is limited to 64 KiB.
Pagination. The endpoint has no cursor or page number: with data.truncated: true, rows beyond the limit are dropped. Fetch the rest with the next query: add a stable ORDER BY sort and LIMIT with OFFSET to sql, or a condition on the key of the last row received, for example WHERE id > ?.
Examples
curl — personal key
curl -X POST "https://vibecode.bitrix24.com/v1/infra/servers/APP_ID/db/query" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sql": "SELECT id, name, email FROM customers WHERE id > ? ORDER BY id",
"parameters": [1]
}'
curl — OAuth application
curl -X POST "https://vibecode.bitrix24.com/v1/infra/servers/APP_ID/db/query" \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"sql": "SELECT id, name, email FROM customers WHERE id > ? ORDER BY id",
"parameters": [1]
}'
JavaScript — personal key
const res = await fetch(
`https://vibecode.bitrix24.com/v1/infra/servers/${appId}/db/query`,
{
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
sql: 'SELECT id, name, email FROM customers WHERE id > ? ORDER BY id',
parameters: [1],
}),
}
)
const { success, data, error } = await res.json()
if (!success) throw new Error(`${error.code}: ${error.details?.reason ?? error.message}`)
// Rows are positional arrays in data.columns order
const rows = data.rows.map(r => Object.fromEntries(data.columns.map((c, i) => [c, r[i]])))
console.log(rows, data.truncated ? 'result truncated' : '')
JavaScript — OAuth application
const res = await fetch(
`https://vibecode.bitrix24.com/v1/infra/servers/${appId}/db/query`,
{
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
sql: 'SELECT id, name, email FROM customers WHERE id > ? ORDER BY id',
parameters: [1],
}),
}
)
const { success, data, error } = await res.json()
if (!success) throw new Error(error.code)
console.log(data.columns, data.rows)
Response fields
| Field | Type | Description |
|---|---|---|
Fieldsuccess |
Typeboolean | Descriptiontrue on success |
Fielddata.file |
Typestring | DescriptionPath of the file read, relative to /data |
Fielddata.columns |
Typearray | DescriptionResult column names in order. Duplicate names, for example after a join, remain separate items |
Fielddata.rows |
Typearray | DescriptionResult rows, up to 500. Each row is an array of values in data.columns order |
Fielddata.rows[][] |
Typestring | number | null | object | DescriptionCell value. Text as a string, numbers as a number, SQL NULL as null. Values that cannot be passed as a JSON number or string come as an object with a type field — see the three rows below |
Fielddata.rows[][] with type: "blob" |
Typeobject | DescriptionBinary data; the base64 field holds the Base64-encoded content |
Fielddata.rows[][] with type: "integer" |
Typeobject | DescriptionAn integer with absolute value greater than 9,007,199,254,740,991; the value field holds the number as a string |
Fielddata.rows[][] with type: "float" |
Typeobject | DescriptionInfinity or not-a-number; the value field is Infinity, -Infinity or NaN |
Fielddata.rowCount |
Typeinteger | DescriptionNumber of rows in data.rows |
Fielddata.truncated |
Typeboolean | Descriptiontrue if the result was truncated: more than 500 rows or the response approached 1 MiB. Rows beyond the limit are not returned |
Fielddata.durationMs |
Typenumber | DescriptionRead time on the host in milliseconds |
Response example
{
"success": true,
"data": {
"file": "app.db",
"columns": ["id", "name", "email"],
"rows": [
[2, "Customer B", "b@example.com"],
[3, "Customer C", null]
],
"rowCount": 2,
"truncated": false,
"durationMs": 0.75
}
}
Error response example
422 — the query tries to change data:
{
"success": false,
"error": {
"code": "APP_DB_SQL_ERROR",
"message": "SQL operation is not permitted",
"details": {
"reason": "not_permitted"
}
}
}
An SQL error response contains neither the query text, nor the values, nor the original SQLite message — only error.details.reason names the cause.
Errors
| HTTP | Code | Description |
|---|---|---|
| HTTP400 | CodeVALIDATION_ERROR |
Descriptionsql is missing, an unknown field was passed, parameters has more than 256 items or contains an invalid value, sql or file exceeds the allowed length |
| HTTP400 | CodeAPP_DB_INVALID_FILE |
Descriptionfile is an absolute path, contains . or .., goes through a symbolic link or does not point to an SQLite database |
| 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 galaxy host is asleep and the balance is frozen — the host cannot be woken |
| HTTP403 | CodeINFRA_SCOPE_REQUIRED |
DescriptionThe key lacks the vibe:infra scope |
| HTTP403 | CodeAPP_DB_DISABLED |
DescriptionDatabase access is not enabled for the portal |
| HTTP403 | CodeSERVER_WAKE_BLOCKED |
DescriptionThe galaxy host is asleep and waking it is blocked |
| HTTP404 | CodeNOT_FOUND |
DescriptionThe application does not exist, was deleted or is not accessible to this key |
| HTTP404 | CodeAPP_DB_NOT_FOUND |
DescriptionThere is no /data volume or database file — the application has not created a database yet |
| HTTP409 | CodeAPP_DB_WAL_CLOSED |
DescriptionThe database is in WAL mode, but the -wal and -shm files are missing: a running application has closed the connection. There is no Retry-After header |
| HTTP409 | CodeAPP_DB_BUSY |
DescriptionThe host is busy with another operation, did not connect within 10 seconds, or the database is locked by an application write. The response carries a Retry-After header |
| HTTP413 | CodePAYLOAD_TOO_LARGE |
DescriptionThe request body exceeds 64 KiB, or the request together with service data exceeds 96 KiB |
| HTTP422 | CodeAPP_DB_SQL_ERROR |
Descriptionerror.details.reason is not_permitted: the statement tries to change data or the schema, control a transaction, attach another database, run PRAGMA or load an extension, or call readfile or writefile |
| HTTP422 | CodeAPP_DB_SQL_ERROR |
Descriptionerror.details.reason is syntax_error: an SQL syntax error |
| HTTP422 | CodeAPP_DB_SQL_ERROR |
Descriptionerror.details.reason is no_such_table: a table from the query does not exist in the database |
| HTTP422 | CodeAPP_DB_SQL_ERROR |
Descriptionerror.details.reason is no_such_column: a column from the query does not exist in the table |
| HTTP422 | CodeAPP_DB_SQL_ERROR |
Descriptionerror.details.reason is type_mismatch: a value is incompatible with the type |
| HTTP422 | CodeAPP_DB_SQL_ERROR |
Descriptionerror.details.reason is other: other execution errors, including several statements in one sql |
| HTTP422 | CodeAPP_DB_QUERY_TIMEOUT |
DescriptionThe query did not finish within 5 seconds |
| HTTP422 | CodeAPP_DB_CORRUPT |
DescriptionThe database integrity check failed |
| HTTP429 | CodeRATE_LIMITED |
DescriptionThe read request limit for the portal was exceeded — Limits. The pause is in the Retry-After header |
| HTTP503 | CodeAPP_DB_TOOL_UNAVAILABLE |
DescriptionThe host is not prepared for database reads or is temporarily unavailable. The response carries a Retry-After header. Preparation — Prepare the host |
| HTTP503 | Codeuser_self_deletion_pending |
DescriptionThe key owner is deleting their account on this portal |
Full list of common API errors — Errors.
Known specifics
- The query cannot change the database. The connection is opened read-only, and in addition to the server-side statement check, SQLite itself rejects writes. That is why, unlike write SQL,
POST /db/querydoes not create a protective backup before running. - SQL and data are not stored in the audit log. The log records the operation, the row count and the duration. The
sqltext,parametersand result rows are not stored there.