Skip to main content
Every table in the Data Model generates a REST query surface. This page covers the read patterns you’ll use most. The examples assume a 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.
A missing record returns 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.
To get the total record count, send Prefer: count=exact (slower) or count=estimated (fast, approximate).

Filtered list

Filters are query parameters in the form ?fieldName=operator.value.
Multiple parameters AND together by default.

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.
The dot-style alternative (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.
Parentheses embed fields from related tables — equivalent to a SQL JOIN or a nested GraphQL query.
You can nest up to 3 levels deep, with a maximum of 10 relations per query. Exceeding either limit returns 400 Bad Request.

Field aliases

Use originalName: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.
Use the advanced endpoint when: 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

1000 with limit=1000. The default is 100. For larger result sets, paginate.
By default it’s omitted for performance. Send Prefer: count=exact for the precise total or count=estimated for a fast approximation.
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.
URL params for simple, browser-friendly, cacheable queries. _query for complex nested logic, dynamic query construction, or filters that exceed URL length limits.
Embed the join table — for example, ?select=id,name,enrollments(course(name)) — and the parentheses traverse the relationship. Up to 3 levels deep.