## Get table rows

`GET /v1/infra/servers/:id/db/tables/:table/rows`

Returns rows of a table or view from the application's SQLite database page by page, with sorting by a column and one filter. Along with the rows come the keys used to update or delete an individual row.

When the endpoint responds — see [When the database is available](/docs/infra/database#when-the-database-is-available).

## Parameters

| Parameter | Type | Required | Default | Description |
|----------|-----|:-----:|-----------|----------|
| `id` (path) | string | yes | — | ID of an application with `kind` equal to `GALAXY_APP`. List: [`GET /v1/infra/servers`](/docs/infra/servers/list) |
| `table` (path) | string | yes | — | Exact table or view name, up to 256 bytes in UTF-8. List: the `data.tables[].name` field in [`GET /db/schema`](/docs/infra/database/schema) |
| `file` (query) | string | no | file from `VIBE_DB_PATH`, otherwise `app.db` | Database file path relative to `/data`, up to 1024 bytes in UTF-8. File list: the `data.files[].file` field in [`GET /db`](/docs/infra/database/info). An absolute path, a path outside `/data` and a symbolic link return `400 APP_DB_INVALID_FILE` |
| `limit` (query) | number | no | `100` | Rows per page, from `1` to `500` |
| `offset` (query) | number | no | `0` | How many rows to skip, from `0` to `1000000` |
| `sort` (query) | string | no | — | Sort column. Take the name from `data.tables[].columns[].name` in [`GET /db/schema`](/docs/infra/database/schema) |
| `order` (query) | string | no | `asc` | Sort direction: `asc` or `desc` |
| `filterColumn` (query) | string | no | — | Column used to filter rows. Take the name from the same schema as for `sort` |
| `filterOp` (query) | string | no | — | Filter condition: `eq` — the column value equals `filterValue`, `contains` — the column value cast to text contains `filterValue` as a case-sensitive substring. The `%` and `_` characters in `contains` are matched as regular characters, not as a pattern |
| `filterValue` (query) | string | no | — | Value to compare, up to 16,384 characters. The whole query string also limits the length: a value of 8,000 Latin characters passes, while a value of 12,000 gets a `414` response without a JSON body. An empty string is a separate value, not the absence of a value |
| `filterValueIsNull` (query) | string | no | — | `true` — select rows where `filterColumn` holds the SQL value `NULL`. Passed instead of `filterValue` and only with `filterOp=eq` |

The filter is passed in full or not at all: `filterColumn`, `filterOp` and one of the two — `filterValue` or `filterValueIsNull=true`. An incomplete filter, both values at once, `filterValueIsNull=true` with `contains`, an out-of-range value and any parameter not listed in the table return `400 VALIDATION_ERROR`. A column name in `sort` or `filterColumn` that does not exist in the table returns `404 APP_DB_TABLE_NOT_FOUND`.

**Pagination.** One call returns at most `limit` rows and at most 1 MiB of JSON. `data.truncated` is `true` when there are more rows beyond the page or the page was truncated by size — in that case `data.rowCount` may be less than `limit`. Request the next page with `offset` equal to `data.offset + data.rowCount`.

**Row order in a table and in a view.** In a table, rows are sorted by `sort`, then by the primary key columns in ascending order, then by the hidden `rowid` if present. Without `sort`, the order is set by the primary key or `rowid`, so pages fetched through `offset` do not overlap or lose rows as long as the data does not change. A view has no such additional order: pages of a view fetched through `offset` may repeat or skip rows.

## Examples

### curl — personal key

```bash
curl "https://vibecode.bitrix24.com/v1/infra/servers/7c2b1f08-3a4d-4e91-9b6c-2f5e8a1d0c33/db/tables/customers/rows?limit=2" \
  -H "X-Api-Key: YOUR_API_KEY"
```

### curl — OAuth application

```bash
curl "https://vibecode.bitrix24.com/v1/infra/servers/7c2b1f08-3a4d-4e91-9b6c-2f5e8a1d0c33/db/tables/customers/rows?limit=2" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN"
```

### JavaScript — personal key

```javascript
const appId = '7c2b1f08-3a4d-4e91-9b6c-2f5e8a1d0c33'
const res = await fetch(`https://vibecode.bitrix24.com/v1/infra/servers/${appId}/db/tables/customers/rows?limit=2`, {
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
  },
})

const { success, data } = await res.json()
console.log(data.columns, data.rows)
```

### JavaScript — OAuth application

```javascript
const appId = '7c2b1f08-3a4d-4e91-9b6c-2f5e8a1d0c33'
const res = await fetch(`https://vibecode.bitrix24.com/v1/infra/servers/${appId}/db/tables/customers/rows?limit=2`, {
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
  },
})

const { success, data } = await res.json()
```

## Response fields

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | Always `true` on success |
| `data.file` | string | Database file the rows were read from — a path relative to `/data` |
| `data.table` | string | Table or view name from the request |
| `data.columns` | array | Column names in table order. Duplicate names remain separate items |
| `data.rows` | array | Rows on the page. Each row is an array of values in `data.columns` order |
| `data.rows[][]` | string, number, null, object | Cell value. An object comes in three cases: `{ "type": "blob", "base64": "..." }` — binary data in base64, `{ "type": "integer", "value": "..." }` — an integer with absolute value greater than 9,007,199,254,740,991 as a string, `{ "type": "float", "value": "NaN" }` — a non-numeric value `NaN`, `Infinity` or `-Infinity` |
| `data.rowKeys` | array | Row keys, one per row of `data.rows` in the same order |
| `data.rowKeys[]` | string, null | Opaque row key up to 1024 characters — the value for `:key` in [`PATCH /db/tables/:table/rows/:key`](/docs/infra/database/rows-update) and [`DELETE /db/tables/:table/rows/:key`](/docs/infra/database/rows-delete). The key is bound to the application, file and table it was read from. Take it from the response as is; do not build it from column values. `null` — the row cannot be changed by key: it is a row of a view or virtual table, the table has no primary key and no hidden `rowid`, the primary key column of this row holds `NULL` or a binary value, or the key is longer than 1024 characters |
| `data.rowCount` | number | Number of rows on this page |
| `data.limit` | number | The `limit` value the request ran with |
| `data.offset` | number | The `offset` value the request ran with |
| `data.truncated` | boolean | `true` — there are more rows beyond the page or the page was truncated at 1 MiB |
| `data.durationMs` | number | Read time in milliseconds |

## Response example

The `customers` table with the `id` primary key and three rows, request with `limit=2`:

```json
{
  "success": true,
  "data": {
    "file": "app.db",
    "columns": [
      "id",
      "name",
      "email",
      "created_at"
    ],
    "rows": [
      [
        1,
        "Customer A",
        "a@example.com",
        "2026-10-20 08:12:46"
      ],
      [
        2,
        "Customer B",
        "b@example.com",
        "2026-10-20 08:12:46"
      ]
    ],
    "rowCount": 2,
    "truncated": true,
    "durationMs": 1.114,
    "rowKeys": [
      "eyJ2IjoxLCJhcHBJZCI6IjdjMmIxZjA4LTNhNGQtNGU5MS05YjZjLTJmNWU4YTFkMGMzMyIsImZpbGUiOiJhcHAuZGIiLCJ0YWJsZSI6ImN1c3RvbWVycyIsImtpbmQiOiJwayIsImNvbHVtbnMiOlsiaWQiXSwidmFsdWVzIjpbeyJ0eXBlIjoiaW50ZWdlciIsInZhbHVlIjoiMSJ9XX0",
      "eyJ2IjoxLCJhcHBJZCI6IjdjMmIxZjA4LTNhNGQtNGU5MS05YjZjLTJmNWU4YTFkMGMzMyIsImZpbGUiOiJhcHAuZGIiLCJ0YWJsZSI6ImN1c3RvbWVycyIsImtpbmQiOiJwayIsImNvbHVtbnMiOlsiaWQiXSwidmFsdWVzIjpbeyJ0eXBlIjoiaW50ZWdlciIsInZhbHVlIjoiMiJ9XX0"
    ],
    "table": "customers",
    "limit": 2,
    "offset": 0
  }
}
```

The `active_customers` view, request without parameters — view rows cannot be edited by key, so `rowKeys` contains `null`:

```json
{
  "success": true,
  "data": {
    "file": "app.db",
    "columns": [
      "id",
      "name"
    ],
    "rows": [
      [
        1,
        "Customer A"
      ],
      [
        2,
        "Customer B"
      ]
    ],
    "rowCount": 2,
    "truncated": false,
    "durationMs": 0.965,
    "rowKeys": [
      null,
      null
    ],
    "table": "active_customers",
    "limit": 100,
    "offset": 0
  }
}
```

## Error response example

404 — the database has no table with this name:

```json
{
  "success": false,
  "error": {
    "code": "APP_DB_TABLE_NOT_FOUND",
    "message": "Table or column not found"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `VALIDATION_ERROR` | A parameter is out of range, unknown, or the filter is incomplete. Message `Invalid database read parameters` |
| 400 | `VALIDATION_ERROR` | The table name in `:table` is longer than 256 bytes. Message `Invalid table name` |
| 400 | `APP_DB_INVALID_FILE` | Invalid path in `file`: absolute, outside `/data` or through a symbolic link |
| 400 | `APP_DB_UNSUPPORTED_PLACEMENT` | The application is not hosted in Galaxy |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header is missing |
| 401 | `INVALID_API_KEY` | The key was not found or is invalid |
| 402 | `BILLING_FROZEN` | The galaxy host is asleep and cannot be woken: the account balance is frozen |
| 403 | `INFRA_SCOPE_REQUIRED` | The key lacks the `vibe:infra` scope |
| 403 | `APP_DB_DISABLED` | Application database access is not enabled for the portal |
| 403 | `SERVER_WAKE_BLOCKED` | The galaxy host is asleep and waking it is blocked |
| 404 | `NOT_FOUND` | The application was not found, was deleted or is not accessible to this key |
| 404 | `APP_DB_NOT_FOUND` | The application has no `/data` volume or database file — the application has not created a database yet |
| 404 | `APP_DB_TABLE_NOT_FOUND` | The database has no `:table` table or view, or no column from `sort` or `filterColumn` |
| 409 | `APP_DB_BUSY` | The host is busy with another operation, has not woken up yet, or the database is busy with an application write. Retry after the delay from the `Retry-After` header |
| 409 | `APP_DB_WAL_CLOSED` | A running application has closed its database connection. A retry without changing the application returns the same error — see [When the database is available](/docs/infra/database#when-the-database-is-available) |
| 414 | — | The query string exceeds the server limit; the response is an HTML page without JSON. Shorten `filterValue` |
| 422 | `APP_DB_QUERY_TIMEOUT` | The read did not finish within the allotted time |
| 422 | `APP_DB_CORRUPT` | The database file is corrupted. Restore the database from a backup — [Restore the database from a backup](/docs/infra/database/restore) |
| 429 | `RATE_LIMITED` | The read request limit for the portal was exceeded — [Limits](/docs/infra/database#limits). Retry after the delay from the `Retry-After` header |
| 503 | `APP_DB_TOOL_UNAVAILABLE` | The host is not prepared for database reads or is temporarily unavailable. Retry after the delay from the `Retry-After` header. Host readiness — [`GET /app-db/prepare`](/docs/infra/database/prepare-status) |
| 503 | `user_self_deletion_pending` | The key owner is deleting their account on this portal |

Full list of common API errors — [Errors](/docs/errors).

## See also

- [Get the database schema](/docs/infra/database/schema)
- [Run a read-only SQL query](/docs/infra/database/query)
- [Add a row](/docs/infra/database/rows-create)
- [Update a row](/docs/infra/database/rows-update)
- [Delete a row](/docs/infra/database/rows-delete)
- [Get database information](/docs/infra/database/info)
- [Application database](/docs/infra/database)
- [Errors](/docs/errors)
