---
title: "List tables (cursor-paginated)"
method: GET
path: "/v2/storage/branch/{branchId}/table-list"
tags: ["Tables"]
---

# List tables (cursor-paginated)

`GET /v2/storage/branch/{branchId}/table-list`

## Request

- Base URL: `https://connection.keboola.com`
- URL: `https://connection.keboola.com/v2/storage/branch/{branchId}/table-list`
- Auth: one of:
  - API key in header `X-StorageApi-Token`
  - API key in header `X-KBC-ManageApiToken`
  - HTTP bearer
- TypeScript: <https://skmtc.dev/keboola/apis/keboola-storage-api/docs/v2/storage/branch/:branchId/table-list/get.ts> (a function with typed inputs; its response is checked against the schema)

Lists the tables of the branch the token can read, one page at a time. `default` as the branch ID lists
production. A development branch is listed over production: its own tables, and each production table
the branch has no table of the same ID for in a bucket the token can read. `branchId` of an item is the
branch the table belongs to: an inherited table carries the ID of the default branch, and its detail is
read through that branch.

Returns a cursor-paginated collection: `data` holds the tables on the page, `meta` the `total` matching the
filters regardless of pagination, the applied `limit` and the structure of the `cursor`, and `links`
absolute URLs of this endpoint (`self`, `first`, `next`) that keep every active filter. `next` is `null`
on the last page.

An item is deliberately lighter than the table detail: it carries the fields needed to list and pick
tables. `include` adds detail fields on request; fetch the table detail (`GET /v2/storage/tables/{tableId}`)
for column metadata and everything else.

#### Include

*include* - comma-separated, each value at most once; a field not requested is missing from the item:

* *columns* - `columns`, the column names in the order of the table detail.
* *metadata* - `metadata`, the table-level metadata as `GET /v2/storage/tables/{tableId}/metadata` returns
  it; on an alias that syncs its columns also `sourceTable.metadata`, the table-level metadata of its root
  source table (in another project too), and `null` there for an alias that does not sync its columns.
* *definition* - `definition`, the table definition equal to the `definition` of the table detail, built
  from the native column and table fields only. An alias that syncs its columns (a linked-bucket table too)
  takes its column types from its root source table and resolves descriptions along its chain, as the
  detail does. `null` for a typed table whose column types are not all on the native fields yet, and for
  an alias of such a table; the detail reads the legacy metadata for them. The table `description` comes
  from the native field only: the detail falls back to the legacy `KBC.description` table metadata when
  the native field is empty, the list does not.

#### Filtering

Filters use the JSON:API `filter` family and combine with AND:

* *filter[bucketId]* - ID of one bucket, e.g. `in.c-main`. In a development branch it lists the branch's
  tables of that bucket and the ones it inherits from production. A bucket outside the list or one the
  token cannot read answers 404.
* *filter[ids]* - comma-separated table IDs, at most `page[limit]` of them.
* *filter[name]* - words separated by whitespace; a table matches when every word is contained in its ID
  (`in.c-main.orders`), its display name or its bucket's display name, ignoring case and accents, in any
  order. `%` and `_` match themselves. A blank value is no filter; the length and the number of words are
  limited by the server. It narrows the listing and keeps its order, cursor and `meta.total`; it does not
  rank, match fuzzily or search columns or metadata.
* *filter[lastUpdatedBy]* - comma-separated `componentId:configurationId` pairs, e.g.
  `keboola.ex-db-snowflake:123,keboola.snowflake-transformation:456`; unlike `filter[name]`, a blank value is
  rejected. The length of each ID, the number of pairs and the number of distinct components among them
  are limited by the server. A table matches when its own `lastUpdatedBy` has the component ID and the configuration ID of one
  of the pairs, whatever its configuration row and branch. An alias that syncs its columns (a linked-bucket
  table too) never matches: it shows its root table's `lastUpdatedBy`, and the configuration wrote that
  root, which matches in its own project. An alias that does not sync its columns matches on its own values.
* *filter[sourceTableId]* - ID of a table of the project, e.g. `in.c-main.users`: only the aliases whose direct
  source it is (an alias of an alias names its own source). In a development branch the branch's table of
  that ID and the production one (only the branch's own when `filter[inheritProduction]` is false). An ID of
  no table of the project lists nothing; a value that is no table ID is rejected.
* *filter[changedSince]* - a unix timestamp or anything `strtotime()` reads, the rule the `changedSince` of
  the table export validates with: e.g. `2026-10-28T10:15:00+01:00`, `1761642900` or `-1 day`. Unlike the
  export, `0` is the epoch and a date before 1970 written as a string is accepted. A value without an
  offset is read in the server's zone (currently Europe/Prague). Times after the year 9999 are
  rejected. A relative value resolves on each request:
  `links.next` carries the resolved time (ISO 8601 with offset), so every page reached through it shares
  one cutoff, while `links.self` and `links.first` keep the value sent; a client that builds `page[after]`
  itself sends that resolved time on later pages. It keeps only the tables whose own
  record changed at or after it (an import, a rename, the description, the primary key, an added or deleted
  column, the statistics). Send the previous poll's time minus a small overlap and deduplicate by `id`.
  Deleted tables are not returned: compare `meta.total` of the unfiltered listing with the tables you hold.
  Changes kept only outside the table record (metadata, a column's type or description) do not count.
* *filter[inheritProduction]* - `true` (default) or `false`. `false` lists only the development branch's own
  tables; the default branch ignores it.

#### Pagination

Tables are ordered `bucket.stage ASC, bucket.name ASC, name ASC`, the order of their IDs.

* *page[limit]* - number of returned tables; the default and the maximum are configured on the server.
  A larger value is rejected, never clamped.
* *page[after]* - cursor of the last table already read; omit it for the first page. It is the base64url
  (padding optional) of the JSON object `{"bucket.stage": <string>, "bucket.name": <string>, "name": <string>}`
  holding that table's `bucket.stage`, `bucket.name` and `name` as the list prints them, e.g.
  `{"bucket.stage":"in","bucket.name":"c-main","name":"users"}` → `eyJidWNrZXQuc3RhZ2UiOiJpbiIsImJ1Y2tldC5uYW1lIjoiYy1tYWluIiwibmFtZSI6InVzZXJzIn0`.
  `links.next` already carries the cursor of the page's last table; `meta.cursor` repeats this structure.

An undecodable cursor, bare `limit` / `offset`, `page[offset]`, an unknown `page[...]` or `filter[...]`
member, an unknown or repeated `include` value and any other query parameter are rejected with a
validation error.

## Path parameters

- `branchId` string, required

## Query parameters

- `page` object
  - `after` string: base64url(json) of `{"bucket.stage": string, "bucket.name": string, "name": string}`.
  - `limit` integer
- `include` string[]
- `filter` object
  - `bucketId` string: ID of one bucket, e.g. `in.c-main`.
  - `ids` string: Comma-separated table IDs, at most `page[limit]` of them.
  - `sourceTableId` string: ID of a table of the project: only its direct aliases.
  - `changedSince` string: Only the tables whose record changed at or after this time: a unix timestamp or anything strtotime() reads, e.g. `-1 day`; `links.next` carries the resolved absolute time. Deleted tables are not returned.
  - `name` string: Words that must all be contained in the table ID, the table display name or the bucket display name, ignoring case and accents.
  - `lastUpdatedBy` string: Comma-separated `componentId:configurationId` pairs; a blank value is rejected. A table matches one of them with its own `lastUpdatedBy`. An alias that syncs its columns never matches.
  - `inheritProduction` boolean: List a development branch over production; `false` lists only its own tables. Ignored in the default branch.

## Response `200`

Tables on the requested page.

- TableListV2Response
  - `data` TableListV2ItemResponse[], required: Tables on the requested page.
    - `id` string, required: Table ID.
    - `bucketId` string, required: ID of the table bucket.
    - `name` string, required: Table name.
    - `displayName` string, required: Table display name.
    - `branchId` integer, required: ID of the branch the row comes from, the branch of its bucket.
    - `bucket` TableListV2BucketResponse, required: The bucket the table row comes from.
      - `id` string, required: Bucket ID.
      - `name` string, required: Bucket name.
      - `displayName` string, required: Bucket display name.
      - `stage` 'in' | 'out' | 'sys', required: Bucket stage.
      - `backend` string, required: Storage backend of the bucket.
      - `idBranch` integer, required: ID of the branch the bucket belongs to.
    - `primaryKey` string[], required: Primary key column names.
    - `isAlias` boolean, required: Whether the table is an alias.
    - `isAliasable` boolean, required: Whether an alias can be created from the table: true for a table that is not an alias, and for an alias that follows the source columns and is neither filtered nor a custom-SQL alias.
    - `isTyped` boolean, required: Whether the table is typed.
    - `tableType` string, required: Kind of the backend object.
    - `rowsCount` integer, nullable, required: Number of rows; `null` for a filtered alias.
    - `dataSizeBytes` integer, nullable, required: Data size in bytes; `null` for a filtered alias.
    - `created` string, date-time, nullable, required: Creation time.
    - `lastImportDate` string, date-time, nullable, required: Last import time.
    - `lastChangeDate` string, date-time, nullable, required: Last change time.
    - `description` string, nullable, required: Table description, from the native description field only: a description kept only in the legacy `KBC.description` metadata is not shown. An alias that syncs its columns falls back to the description of its source chain.
    - `isDescriptionSystemManaged` boolean, required: Whether the description is maintained by the system rather than a user.
    - `createdBy` TableAttributionResponse, required: The component, configuration, configuration row and branch recorded as having created or last written the table. Informational only: it mirrors the provider `system` `KBC.createdBy.*` / `KBC.lastUpdatedBy.*` table metadata, which Output Mapping writes and any token with write access to the bucket can also set through the metadata API. An alias with `aliasColumnsAutoSync: true`, including a table of a linked bucket, shows the values of its root table; an alias with a curated column set shows its own values.
      - `componentId` string, nullable, required: Component ID.
      - `configurationId` string, nullable, required: Configuration ID; `null` for a write without a configuration, such as a workspace unload.
      - `configurationRowId` string, nullable, required: Configuration row ID; set for row-based configurations only.
      - `branchId` string, nullable, required: Branch ID; set only when the write came from a development branch.
    - `lastUpdatedBy` TableAttributionResponse, required: The component, configuration, configuration row and branch recorded as having created or last written the table. Informational only: it mirrors the provider `system` `KBC.createdBy.*` / `KBC.lastUpdatedBy.*` table metadata, which Output Mapping writes and any token with write access to the bucket can also set through the metadata API. An alias with `aliasColumnsAutoSync: true`, including a table of a linked bucket, shows the values of its root table; an alias with a curated column set shows its own values.
      - `componentId` string, nullable, required: Component ID.
      - `configurationId` string, nullable, required: Configuration ID; `null` for a write without a configuration, such as a workspace unload.
      - `configurationRowId` string, nullable, required: Configuration row ID; set for row-based configurations only.
      - `branchId` string, nullable, required: Branch ID; set only when the write came from a development branch.
    - `sourceTable` TableListV2SourceTableResponse: The table an alias reads from.
      - `id` string, required: Source table ID.
      - `project` TableListV2SourceProjectResponse, required: The project of an alias source table.
        - `id` integer, required: Project ID.
        - `name` string, nullable, required: Project name as recorded when the alias was created.
      - `metadata` TableListV2MetadataResponse[], nullable: With `include=metadata` only: the table-level metadata of the root source table; `null` for an alias that does not sync its columns.
        - `id` string, required: Metadata record identifier.
        - `key` string, required: Metadata key.
        - `value` string, nullable, required: Metadata value.
        - `provider` string, required: ID of the component that created the metadata.
        - `timestamp` string, required: When the metadata was created or last updated, ISO 8601 with a `+hhmm` offset; empty when unknown.
    - `aliasColumnsAutoSync` boolean, nullable: Aliases other than custom-SQL aliases only: whether the alias follows the source columns.
    - `aliasFilter` TableListV2AliasFilterResponse: The row filter of a filtered alias.
      - `column` string, required: Filtered column.
      - `operator` 'eq' | 'ne', nullable, required: Comparison operator.
      - `values` unknown[], nullable, required: Values the column is compared with, as stored; older filters may hold numbers.
        - unknown
    - `selectSql` string, nullable: Custom-SQL aliases only: the SQL of the alias.
    - `columns` string[], nullable: With `include=columns` only: the column names in the order of the table detail.
    - `metadata` TableListV2MetadataResponse[], nullable: With `include=metadata` only: the table-level metadata, as `GET /v2/storage/tables/{tableId}/metadata` returns it.
      - `id` string, required: Metadata record identifier.
      - `key` string, required: Metadata key.
      - `value` string, nullable, required: Metadata value.
      - `provider` string, required: ID of the component that created the metadata.
      - `timestamp` string, required: When the metadata was created or last updated, ISO 8601 with a `+hhmm` offset; empty when unknown.
    - `definition` TableDefinitionResponse: Native table definition, in the same shape as the create-table-from-definition endpoint. Built by Keboola\Connection\Storage\Service\Metadata\MetadataConvertor::createTableDefinitionResponseFromMetadata() for a typed table, or ::createUntypedTableDefinitionResponseFromMetadata() for an untyped one - for an untyped table, `basetype`, `canBeFiltered`, `distribution` and `index` are never present, and each column `definition` only ever carries `description`. A storage driver may add further backend-specific keys via its extendTableDefinitionResponse() hook, e.g. BigQuery adds `timePartitioning`, `clustering`, `rangePartitioning` and `requirePartitionFilter`. For an alias with auto-synced columns (including a linked-bucket table), column types come from the root source table and table and column descriptions are resolved along the alias chain, the alias's own value first.
      - `primaryKeysNames` string[], required: Primary key column names.
      - `columns` object[], required
        - `name` string, required: Column name.
        - `canBeFiltered` boolean: Whether the column type can be used in a `where` filter. Absent for an untyped table.
        - `definition` object, required: Column type definition. An empty object (`{}`) for an untyped column without a description.
          - `type` string: Backend-native column type. Absent for an untyped table.
          - `nullable` boolean: Whether the column accepts NULL. Absent for an untyped table.
          - `length` string, nullable: Backend-native type length/precision, for example `38,9`. Absent when not applicable.
          - `default` string, nullable: Default value of the column. Absent when not set.
          - `description` string: Column description.
        - `basetype` string, nullable: Keboola base type. Absent for an untyped table.
        - `policyTagState` 'protected' | 'unprotected' | 'unknown': Whether Keboola knows the column to be covered by a BigQuery policy tag, as of the last time the table was reflected. `protected` - a policy tag was observed on the column; `unprotected` - the table was observed and the column carried none; `unknown` - the table has never been observed, or the column carries protection Keboola cannot state (a tag on a field inside a STRUCT, or more than one tag). Present on BigQuery tables only, typed and untyped alike; absent on every other backend.
      - `description` string: Table description, when set.
      - `distribution` object: Present only when the table has a distribution type set. Absent for an untyped table.
        - `type` string, required
        - `distributionColumnsNames` string[], required
      - `index` object: Present only when the table has an index type set. Absent for an untyped table.
        - `type` string, required
        - `indexColumnsNames` string[], required
  - `meta` PaginationMeta, required
    - `total` integer, required: Number of items matching the filters, ignoring pagination.
    - `limit` integer, required: Page size applied (`page[limit]`).
    - `cursor` object, required: How to build the `page[after]` cursor from the last row read, without another call.
      - `param` string, required: Query parameter the cursor goes in.
      - `encoding` string, required: base64url (RFC 4648 §5, padding optional) of a JSON object.
      - `fields` object[], required: Keys of the JSON object, each the last row's value of the item field of the same name.
        - `name` string, required
        - `type` 'integer' | 'string' | 'date-time', required
        - `nullable` boolean, required
      - `order` string, required: Order of the rows; the cursor keys follow it.
  - `links` PaginationLinks, required
    - `self` string, uri, required: This page.
    - `first` string, uri, required: The first page (no `page[after]`).
    - `next` string, uri, nullable, required: The page after this one; `null` on the last page.

## Other responses

- `400`: A `page[...]` or `filter[...]` parameter is invalid: `page[limit]` out of range, more table IDs than `page[limit]`, a `filter[name]` over its length or word limit, a `filter[lastUpdatedBy]` that is blank, is not a list of `componentId:configurationId` pairs, is not valid UTF-8, holds an ID over its length limit or more pairs or distinct components than the server allows, a `filter[changedSince]` that is blank, that strtotime() cannot read or that is after the year 9999, an undecodable cursor, an unknown member or parameter, or a bare `limit`, `offset`, `bucketId`, `ids` or `page[offset]`.
- `404`: The branch does not exist in the project, or `filter[bucketId]` is not a bucket of the list the token can read.

---

[API](https://skmtc.dev/keboola/apis/keboola-storage-api.md) - [All operations](https://skmtc.dev/keboola/apis/keboola-storage-api/llms.txt) - [OpenAPI document](https://skmtc.dev/keboola/apis/keboola-storage-api/revisions/34b5ceac0317?raw)
