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.