Railbase
GPTClaude

The REST API

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

Updated

Video guide —watch on YouTube ↗

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

Endpoints

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]

Filters on indexed fields (.Index()/.Unique() in the schema, plus the built-in id, created, updated and tenant_id) are served from the field's secondary index — only the matching candidates are read and decoded instead of the whole collection. Equality conditions benefit most; > / >= / < ranges are accelerated too. Results are identical either way — the index only narrows what gets scanned.

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

The list response is enveloped:

{
  "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:

{
  "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) are populated after the record exists, not inline in the create JSON:

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:

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 v1 target (mode strict). A compat.mode knob exists for compatibility experiments — set it at runtime (railbase config set compat.mode '"both"') or via RAILBASE_COMPAT_MODE, 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 response and realtime payload framing. Build against /api/…. RAILBASE_PB_COMPAT / RAILBASE_PBCOMPAT remain legacy fallbacks, but new deployments should use RAILBASE_COMPAT_MODE.

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.

Was this page helpful?Thanks for your feedback!