> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tokenrip.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Get Table Rows

> GET /v0/artifacts/{publicId}/rows — Paginate through table rows

Retrieve rows from a table artifact with cursor-based pagination. Supports server-side sorting, filtering with comparison operators, column projection, and total counts.

**Auth:** Public — no authentication required.

## Path parameters

| Parameter  | Type   | Required | Description                |
| ---------- | ------ | -------- | -------------------------- |
| `publicId` | string | Yes      | The artifact's public UUID |

## Query parameters

| Parameter             | Type    | Required | Description                                                                                                          |
| --------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `limit`               | number  | No       | Maximum rows to return. Default `100`, max `500`                                                                     |
| `after`               | string  | No       | Cursor UUID — return rows after this row ID                                                                          |
| `before`              | string  | No       | Cursor UUID — return rows *before* this row ID. Cannot be combined with `after`                                      |
| `sort_by`             | string  | No       | Column name to sort by. Also accepts `createdAt`, `updatedAt`, `id`. Default: insertion order                        |
| `sort_order`          | string  | No       | `asc` or `desc`. Default `asc`                                                                                       |
| `fields`              | string  | No       | Comma-separated columns to return in each row's `data`, e.g. `slug,title,excerpt`                                    |
| `include_total`       | boolean | No       | `1` or `true` to also return `total` — the unpaginated count matching the filters                                    |
| `filter.<column>[op]` | string  | No       | Filter on a column. Repeatable; multiple filters are ANDed. The operator suffix is optional and defaults to equality |

Sorting is type-aware: `number` columns sort numerically (not lexicographically), `date` columns sort chronologically, `boolean` columns sort by value. For columns with non-castable values, those rows sort as `NULL` (last). Ordering is total — the `created_at, id` tie-break is guaranteed — so cursors stay stable even when many rows share a sort value.

## Filter operators

| Operator              | Example                               | Meaning                                            |
| --------------------- | ------------------------------------- | -------------------------------------------------- |
| *(none)*              | `filter.status=live`                  | Equal                                              |
| `lt` `lte` `gt` `gte` | `filter.publish_date[lte]=2026-07-20` | Range                                              |
| `ne`                  | `filter.tier[ne]=gold`                | Not equal. Rows **missing** the column still match |
| `in`                  | `filter.tier[in]=gold,silver`         | Any of a comma-separated list                      |
| `contains`            | `filter.tags[contains]=sourcing`      | Case-insensitive substring                         |
| `starts`              | `filter.slug[starts]=how-to`          | Case-insensitive prefix                            |

Comparisons use the column's declared type, so `filter.revenue=100` matches a stored `100.0`, and date ranges compare chronologically rather than as strings. `contains` and `starts` always match raw text.

<Warning>
  A `filter`, `sort_by`, or `fields` naming a column the table does not have returns `400` — it is **not** silently ignored. This matters for draft gating: a mistyped `filter.published=true` previously returned every row, including unpublished ones.
</Warning>

<Tip>
  Use `fields` on listing pages. A blog index needs `slug,title,excerpt,image_url,publish_date` — without projection it also transfers every post's full markdown body.
</Tip>

<CodeGroup>
  ```bash cURL (basic) theme={null}
  curl https://api.tokenrip.com/v0/artifacts/a1b2c3d4-e5f6-7890-abcd-ef1234567890/rows?limit=50
  ```

  ```bash cURL (sort + filter) theme={null}
  curl "https://api.tokenrip.com/v0/artifacts/a1b2c3d4/rows?sort_by=revenue&sort_order=desc&filter.active=true"
  ```

  ```bash cURL (operators + projection + total) theme={null}
  curl "https://api.tokenrip.com/v0/artifacts/a1b2c3d4/rows?filter.published=true&filter.publish_date[lte]=2026-07-20&sort_by=publish_date&sort_order=desc&fields=slug,title,excerpt&include_total=1&limit=20"
  ```
</CodeGroup>

## Example response

```json theme={null}
{
  "ok": true,
  "data": {
    "rows": [
      {
        "id": "row-uuid-1",
        "data": {
          "company": "Acme Corp",
          "revenue": 50000,
          "priority": "high"
        },
        "createdAt": "2026-04-14T08:00:00.000Z",
        "updatedAt": "2026-04-14T08:00:00.000Z"
      },
      {
        "id": "row-uuid-2",
        "data": {
          "company": "Globex Inc",
          "revenue": 75000,
          "priority": "medium"
        },
        "createdAt": "2026-04-14T08:05:00.000Z",
        "updatedAt": "2026-04-14T08:05:00.000Z"
      }
    ],
    "nextCursor": "row-uuid-2",
    "prevCursor": null
  }
}
```

## Response fields

| Field              | Type              | Description                                                                                 |
| ------------------ | ----------------- | ------------------------------------------------------------------------------------------- |
| `rows`             | array             | Array of row objects                                                                        |
| `rows[].id`        | string            | Row UUID                                                                                    |
| `rows[].data`      | object            | Key-value pairs matching the table schema                                                   |
| `rows[].createdAt` | string (ISO 8601) | When the row was created                                                                    |
| `rows[].updatedAt` | string (ISO 8601) | When the row was last modified                                                              |
| `nextCursor`       | string \| null    | Pass as `after` to fetch the next page. `null` when no more rows                            |
| `prevCursor`       | string \| null    | Pass as `before` to fetch the previous page. `null` on the first page                       |
| `total`            | number            | Unpaginated count matching the filters. Present **only** when `include_total` was requested |

<Tip>
  To fetch all rows, keep requesting with `after={nextCursor}` until `nextCursor` is `null`.
</Tip>

## Errors

| Status | Code                    | Cause                                                 |
| ------ | ----------------------- | ----------------------------------------------------- |
| `400`  | `INVALID_FILTER_COLUMN` | A filter names a column not in the table schema       |
| `400`  | `INVALID_SORT_COLUMN`   | `sort_by` names a column not in the table schema      |
| `400`  | `INVALID_FIELD`         | `fields` names a column not in the table schema       |
| `400`  | `INVALID_FILTER`        | A filter key is malformed or uses an unknown operator |
| `400`  | `INVALID_CURSOR`        | The cursor is not a row in this table                 |
| `400`  | `CONFLICTING_CURSORS`   | Both `after` and `before` were supplied               |
