Lists and filters
Every collection answers the same grammar.
GET /gigs?limit=50&offset=0&sort=-date,label&fields=id,label,date&filter[status]=booked&filter[date][on_or_after]=2026-10-01
| Parameter | Meaning |
|---|---|
limit |
Page size. Default 50, maximum 200. |
offset |
Records to skip. meta.total is the full count, so offset + limit < total means there is more. |
sort |
Comma-separated fields. Prefix with - for descending. Each resource lists its sortable fields in the reference. |
fields |
Comma-separated subset to return. id is always included. |
filter[field]=value |
Shorthand for equals. |
filter[field][operator]=value |
Any operator the field's type supports. Several fields are ANDed. |
Operators by field type
| Field type | Operators |
|---|---|
| text (labels, notes, names, emails, phones, urls) | equals, does_not_equal, contains, does_not_contain, starts_with, ends_with, is_empty, is_not_empty |
| number, currency | equals, does_not_equal, greater_than, less_than, greater_than_or_equal_to, less_than_or_equal_to, is_empty, is_not_empty |
| date, time, timestamps | equals, before, after, on_or_before, on_or_after, is_empty, is_not_empty |
| select, status, relation (ids) | equals, does_not_equal, is_empty, is_not_empty |
| multi-select | contains, does_not_contain, is_empty, is_not_empty |
| checkbox | equals, does_not_equal |
Text matching is case-insensitive. is_empty and is_not_empty take true. The reference documents the exact operators of every field, and an unsupported operator answers 400 with the list of supported ones.
Examples
Gigs with no band leader yet, soonest first:
GET /gigs?filter[bandLeaderId][is_empty]=true&filter[date][on_or_after]=2026-10-01&sort=date
Staff who can play a role, in call order:
GET /staff-member-roles?filter[roleId]=<role id>&filter[status]=active&sort=callOrder
Everyone blocked out over a weekend:
GET /staff-member-blockouts?filter[startDate][on_or_before]=2026-12-13&filter[endDate][on_or_after]=2026-12-12
Nested collections
GET /gigs/{id}/products is the same query as GET /gig-products?filter[gigId]=…, with a 404 if the gig does not exist. Every nested route accepts the full grammar on top of the pinned parent, and POST to it creates a child with the parent's id already filled in.