# The REST API

> Auto-generated CRUD endpoints with filtering, sorting, expansion, and paging.

_Updated: 2026-06-10_

Every collection you define gets a complete REST API automatically — no
controllers to write. The endpoints are protected by the [access rules](schema)
on the collection.

## Endpoints

```text
GET    /api/collections/{name}/records           list
POST   /api/collections/{name}/records           create
POST   /api/collections/{name}/records/batch      batch write
GET    /api/collections/{name}/records/{id}       view
PATCH  /api/collections/{name}/records/{id}       update
DELETE /api/collections/{name}/records/{id}       delete
POST   /api/collections/{name}/records/{id}/restore   restore (soft-delete)
```

## Querying a list

The list endpoint takes query parameters:

| Param | Example | Purpose |
|---|---|---|
| `page` / `perPage` | `?page=2&perPage=50` | Offset pagination |
| `filter` | `?filter=status='published' && views>100` | Expression filter |
| `sort` | `?sort=-created,title` | Multi-key sort (`-` = descending) |
| `expand` | `?expand=author` | Inline related records |
| `q` | `?q=railbase` | Full-text search over `FTS()` fields |
| `cursor` | `?cursor=…` | Cursor pagination (`nextCursor` in the response) |
| `count` | `?count=exact` | Fill `totalItems`/`totalPages`: `exact` · `estimate` · `cap[:N]` |

```bash
curl "http://localhost:8095/api/collections/posts/records?filter=status='published'&sort=-created&expand=author&count=exact"
```

The list response is enveloped:

```json
{
  "page": 1,
  "perPage": 30,
  "totalItems": 142,
  "totalPages": 5,
  "items": [ { "id": "…", "title": "…", "expand": { "author": { } } } ]
}
```

> [!NOTE]
> Counting is opt-in: without `count=…`, `totalItems` and `totalPages` are
> `null` — a deliberate default, since an exact count is a full scan on large
> collections.

## Batch writes

`POST /api/collections/{name}/records/batch` takes up to 200 operations and
returns a per-op result list; `atomic: true` makes it all-or-nothing:

```json
{
  "atomic": true,
  "ops": [
    { "action": "create", "data": { "name": "Tech" } },
    { "action": "update", "id": "abc123", "data": { "name": "Life" } },
    { "action": "delete", "id": "def456" }
  ]
}
```

## Files

File fields (`File()` / `Files()` in the [schema](schema)) are populated after
the record exists, not inline in the create JSON:

```text
POST   /api/collections/{name}/records/{id}/files/{field}            multipart, part name "file"
DELETE /api/collections/{name}/records/{id}/files/{field}/{filename}
GET    /api/files/{collection}/{record_id}/{field}/{filename}?token=…&expires=…
```

The upload response (and the record's field value) carries a **signed download
URL** — the token is the auth, so the GET needs no `Authorization` header and
expires after a few minutes; re-read the record for a fresh one.

## Authentication

Send a bearer token from one of the [auth endpoints](authentication):

```bash
curl http://localhost:8095/api/collections/posts/records \
  -H "Authorization: Bearer <token>"
```

The current identity is available in rules as `@request.auth.*` and via
`GET /api/auth/me`.

## URL compatibility

Railbase is wire-compatible with PocketBase-style clients: everything is served
under `/api/…` in PB-compatible shapes, and that's the default and the v1 ship
target (mode **`strict`**). A `compat.mode` knob exists for the planned native
surface — set it at runtime (`railbase config set compat.mode '"both"'`) or via
`RAILBASE_PB_COMPAT`, and read the active mode from `GET /api/_compat-mode`:

- `strict` — PocketBase shapes only, under `/api/…` (default)
- `native` — Railbase-native shapes under `/v1/…` (reserved)
- `both` — both prefixes; shape resolved per request (migration aid)

> [!NOTE]
> The native `/v1/…` route surface is **not mounted yet** — today the modes only
> influence shape details such as the realtime payload framing. Build against
> `/api/…`.

> [!TIP]
> You rarely call these by hand — generate a typed client with `railbase generate
> sdk` and let it do CRUD, auth, and realtime for you. See the
> [TypeScript SDK](typescript-sdk).
