---
title: "List / filter records from a table"
method: GET
path: "/api/db/{project_id}/rest/v1/{table}"
tags: ["Database"]
---

# List / filter records from a table

`GET /api/db/{project_id}/rest/v1/{table}`

## Request

- Base URL: `https://core.blink.new`
- URL: `https://core.blink.new/api/db/{project_id}/rest/v1/{table}`
- Auth: one of:
  - HTTP bearer
  - none

PostgREST-compatible SELECT endpoint backed by Turso SQLite.

camelCase column names in `select`, `order` and filters are automatically
converted to snake_case, matching the request-body behaviour of POST/PATCH.

**Authentication**
- Public tables (configured via security policy) can be read without a token
  (capped at 100 rows per request for unauthenticated callers).
- All other tables require a valid bearer token.
- Service-role tokens bypass row-level security (RLS).

**Row-Level Security (RLS)**
When authenticated with a non-service token and the table is not public,
a `WHERE user_id = <caller_id>` filter is automatically appended
(the owner field name is configurable via the project's security policy).

Tables configured with `read: "membership"` are instead scoped via a
junction table: `WHERE <row_field> IN (SELECT <resource_field> FROM
<membership_table> WHERE <user_field> = <caller_id>)`, so every member of
a scope (e.g. all staff of a hostel) can read its rows.

**Filtering — column operators**

| Syntax | SQL equivalent | Example |
|--------|----------------|---------|
| `?col=value` | `col = 'value'` | `?status=active` |
| `?col=eq.value` | `col = 'value'` | `?status=eq.active` |
| `?col=neq.value` | `col <> 'value'` | `?status=neq.archived` |
| `?col=gt.value` | `col > value` | `?age=gt.18` |
| `?col=gte.value` | `col >= value` | `?score=gte.50` |
| `?col=lt.value` | `col < value` | `?price=lt.100` |
| `?col=lte.value` | `col <= value` | `?quantity=lte.5` |
| `?col=like.pattern` | `col LIKE pattern` | `?name=like.Ali%25` |
| `?col=ilike.pattern` | `col LIKE pattern` | `?name=ilike.%25ali%25` |
| `?col=in.(a,b,c)` | `col IN (a, b, c)` | `?status=in.(active,pending)` |
| `?col=is.null` | `col IS NULL` | `?deleted_at=is.null` |
| `?col=is.true` | `col` is true | `?featured=is.true` |
| `?col=not.op.value` | `NOT (…)` | `?status=not.in.(archived,deleted)` |
| `?not.col=eq.value` | `NOT (col = value)` | `?not.status=eq.archived` |

SQLite's `LIKE` ignores ASCII case, so `like` and `ilike` match alike. `neq` and a
non-empty `not.in` never match a NULL. A boolean value (`eq.true`, `eq.1`, `is.true`, an
`in` item) also matches the text `'true'`/`'false'` this API stores booleans as. Repeat a
column to apply several conditions to it: `?price=gte.10&price=lt.20`. Any other PostgREST
operator (`cs`, `fts`, `eq(any)`, …) and the SDK's `not_in` are a `400` with code
`PGRST100` naming it; a value that does not start with an operator is plain equality.

**Logical groups**
- `?or=(col1.eq.val1,col2.gt.5)` — any condition must match
- `?and=(col1.eq.val1,col2.gt.5)` — all conditions must match
- `?not.or=(…)` / `?not.and=(…)` negate a group; nest groups as `or(…)` / `and(…)`
- Quote values containing `, ( ) & "`: `?or=(name.eq."Smith, John",tier.eq.vip)`
- The older key form `?or(col1.eq.val1,col2.eq.val2)` is still accepted

## Path parameters

- `project_id` string, required
- `table` string, required

## Query parameters

- `select` string
- `order` string
- `limit` integer
- `offset` integer

## Response `200`

Array of matching records

- TableRecord[]

## Other responses

- `400`: Invalid table name, column name, or filter expression
- `401`: Unauthenticated access to a non-public table
- `403`: Table access denied by security policy, or RLS owner column missing
- `404`: No database found for the given project_id
- `500`: Internal server error

---

[API](https://skmtc.dev/blink/apis/blink-apis.md) - [All operations](https://skmtc.dev/blink/apis/blink-apis/llms.txt) - [OpenAPI document](https://skmtc.dev/blink/apis/blink-apis/revisions/4c59c706078e?raw)
