students table with fields like firstName, email, age, isActive, createdAt, and a city relationship.
Single record
GET /api/rest/<table>/<id> returns one record by primary key.
404 with a Problem Details body — see Error handling.
Tables with composite primary keys don’t expose /<id> routes. Use the filtered list or the advanced query endpoint instead.
Record list
GET /api/rest/<table> returns up to 100 records by default, wrapped in a data array with pagination metadata.
Prefer: count=exact (slower) or count=estimated (fast, approximate).
Filtered list
Filters are query parameters in the form?fieldName=operator.value.
Comparison operators
Date helpers
Logical groups
or=(...), and=(...), and not=(...) compose conditions. They can nest.
Filtering across relations
Use dot notation to filter a parent by a related table’s field.Field name conflicts
If a field name collides with a reserved query parameter (select, sort, limit), prefix with col.:
Sorted list
?sort=field sorts ascending. Prefix with - for descending. Comma-separate for multi-column sort.
sort=createdAt.desc,firstName.asc) is also supported.
Paginated list
Two pagination styles, both work on the same endpoint.Offset-based
hasNextPage and hasPreviousPage in the response tell you when to stop.
Cursor-based
For stable pagination on frequently-changing tables, use cursors. Cursor values are opaque, base64-encoded strings — read them from the response, don’t construct them yourself.Field selection
?select=field1,field2 returns only the listed fields.
Embedding related records
Parentheses embed fields from related tables — equivalent to a SQL JOIN or a nested GraphQL query.400 Bad Request.
Field aliases
UseoriginalName:alias to rename fields in the response.
Aggregation
GET /api/rest/<table>/_aggregate computes counts, sums, averages, and other aggregates.
Group by
Filter groups with having
Sort and filter aggregates
sort=-count sorts groups by an aggregate alias. Filters from a regular list query (createdAt=this_month.true) apply before aggregation.
Advanced query endpoint
For deeply nested filter logic that’s awkward to express as URL params,POST /api/rest/<table>/_query accepts a structured JSON body. It’s a POST for ergonomics — but the operation is read-only, no data is modified.
For simple lookups, the
GET form is still better — it’s cacheable and bookmarkable.
Permissions
All query endpoints enforce the per-role permissions in Role-Based Access. Records and fields a role can’t read are silently filtered out of responses.FAQ
What's the maximum number of records per request?
What's the maximum number of records per request?
1000 with
limit=1000. The default is 100. For larger result sets, paginate.Why doesn't `totalCount` appear in my response?
Why doesn't `totalCount` appear in my response?
By default it’s omitted for performance. Send
Prefer: count=exact for the precise total or count=estimated for a fast approximation.Should I use offset or cursor pagination?
Should I use offset or cursor pagination?
Offset is simpler — works for jump-to-page UIs. Cursor is more stable when records are being inserted or deleted between page fetches. Use cursor for infinite-scroll feeds; use offset for paginated tables.
When should I use `_query` vs URL params?
When should I use `_query` vs URL params?
URL params for simple, browser-friendly, cacheable queries.
_query for complex nested logic, dynamic query construction, or filters that exceed URL length limits.How do I select fields from a relation that's many-to-many?
How do I select fields from a relation that's many-to-many?
Embed the join table — for example,
?select=id,name,enrollments(course(name)) — and the parentheses traverse the relationship. Up to 3 levels deep.