Skip to content

Records API

The records API is the main surface for client apps. Every base/auth/view collection exposes the same shape under /api/<collection_name>.

GET /api/<col>

Query params:

ParamTypeDefaultNotes
pageint11-based
perPageint30max enforced server-side
filterexprRule expression language
sortstring-created_atcomma-sep; - prefix = DESC; aliases created/updated
expandstringcomma-sep relation field paths; nested via .
fieldsstringcomma-sep field whitelist for the response
skipTotal1/truefalseomit count for faster paging
nearVectorJSON number[]Vector similarity search; pair with nearVectorField.
nearVectorFieldstringVector field on this collection to search against.
nearLimitint10Top-K results to return (max 1000).
nearMinScorefloatDrop matches with cosine similarity below this.

Response:

{
"data": [ { "id": "...", ... } ],
"page": 1,
"perPage": 30,
"totalItems": 42, // -1 if skipTotal
"totalPages": 2 // -1 if skipTotal
}
Terminal window
?filter=published=true
?filter=author.id=u1 || author.id=u2
?filter=title~"hello" && created>1730000000

Quote string literals with double-quotes. Operators: = != > >= < <= ~ (substring), &&, ||, parentheses.

Terminal window
?sort=-created # newest first
?sort=author,-created # by author asc, then newest first
?sort=-updated # last-modified first

Inline relation targets nested in the response under expand:

Terminal window
?expand=author # one hop
?expand=author.profile # two hops
?expand=author,comments # multiple
{
"id": "p1", "title": "...", "author": "u1",
"expand": {
"author": { "id": "u1", "email": "...", "expand": { "profile": { ... } } }
}
}
Terminal window
?fields=id,title

Returns only id and title for each row. id is always included.

Terminal window
?nearVector=[0.12,-0.05,...]&nearVectorField=embedding&nearLimit=10

The candidate set is filtered by filter + list_rule first (so you can’t read past your access scope), then re-ranked by cosine similarity. Each returned row carries a _score field in [-1, 1]. v1 ranks in-process — suitable up to ~50 K candidates. See Vector search.

GET /api/<col>/<id>

Returns { data: {...} } or 404. Responses carry a weak ETag header derived from the record’s updated_at:

ETag: W/"1714502400"

Pass it back as If-None-Match for a cheap conditional GET (server returns 304 Not Modified when nothing changed), or as If-Match on the next PATCH / DELETE for optimistic concurrency control (see below).

POST /api/<col>
Content-Type: application/json
{ "title": "hello", "body": "world" }

Returns the created record (with id, created, updated). Validation errors return 422 with details: { fieldName: message }.

view collections return 405.

PATCH /api/<col>/<id>
Content-Type: application/json
{ "title": "new title" }

Partial update — fields not in the body stay unchanged. view collections return 405.

DELETE /api/<col>/<id>

Returns { data: null }. View collections → 405. Records referenced by restrict-mode relations → 409 with details listing the blockers.

PATCH / DELETE honour If-Match against the record’s current ETag:

PATCH /api/v1/posts/abc123
If-Match: W/"1714502400"
{ "title": "new title" }
  • Match → proceed; response carries the new ETag.
  • Mismatch → 412 Precondition Failed with the server’s current ETag echoed in the response. Re-fetch, re-merge, and retry.
  • If-Match: * matches any existing record (useful for “create-if-missing, block-if-changed” flows).

Both weak (W/"…") and strong-form ("…") tags compare under RFC 7232’s weak compare semantics, so apps don’t have to track which prefix the server sent.

The official SDK auto-attaches If-Match from a per-record cache after every get / list / update. Disable per call with { ifMatch: false } or override with an explicit string.

When a collection has history_enabled: true, every write produces an append-only audit row. See the Record history concept page for the full overview.

GET /api/<col>/<id>/history?page=1&perPage=50

Returns the change log for one record. Gated by the parent record’s view_rule — if you can view the live record (or the most recent snapshot when it’s been deleted), you can read its history.

{
"data": {
"data": [
{ "id": "...", "op": "update", "snapshot": { ... },
"actor_id": "u-42", "actor_type": "user", "at": 1714502400 }
],
"page": 1, "perPage": 50, "totalItems": 3, "totalPages": 1
}
}

404 when history is disabled on the collection.

POST /api/<col>/<id>/restore?at=<unix-seconds>

Admin-only. Restores the record to the most recent snapshot at or before at. The live record’s updated_at advances and history records the restore itself as a fresh update row.

409 when the record was deleted (v1 limitation — createRecord mints its own id, so re-creating a deleted record with its original id is unsupported).

Pass a Bearer token (user or admin):

Authorization: Bearer <jwt>

@request.auth.id, @request.auth.email, @request.auth.type are then available in API rules.

CodeWhen
200OK (list, get, update, delete)
201Created — POST only
400Malformed request
401Unauthorized (missing/invalid token where required)
403Forbidden (rule failed)
404Collection or record not found
304If-None-Match ETag matched — body omitted
405Write attempted on a view collection
409Delete blocked by a restrict cascade · or restore-of-deleted record
412If-Match precondition failed — response echoes the current ETag
422Validation failed (details per field)
429Rate limit exceeded
  • Rules — gating list/view/create/update/delete
  • Files — uploads attached to records
  • Realtime — live updates over WebSocket
  • Batch API — atomic multi-op transactions