Skip to main content
Every list endpoint takes the same query options, so what you learn here applies to all of them. POST /{resource}/query accepts the same options in the body when your query is too long for a URL.

Pagination

  • page starts at 1, and defaults to 1. Pages are 1-based, so page=0 is rejected with a 400.
  • pageSize defaults to 100 when you send no pagination parameters at all. Send it explicitly rather than relying on the default, which differs between the standard endpoints and /timesheet.
  • The response includes hasMore, so page until it is false rather than counting first.
GET /timesheet applies no default page size. Without an explicit pageSize it returns every matching record in one response and reports hasMore: false. Always pass pageSize there.

Sorting

Use comma-separated values (orderBy=name:asc,createdAt:desc) or repeated orderBy parameters to sort by multiple fields, in order. The bracket-array form above also works.

Returning only some fields

Use comma-separated values or repeat select once per field. These forms are equivalent:
In a POST /{resource}/query body, pass an array instead:
An explicit select restricts the resource fields returned, including computed fields on deliverables. You can request computed fields by name. Omit select to read the full record. Nested paths such as data.properties.squareFeet work too.
Append :column1,column2 to search specific columns. Each resource page lists its search columns, and searching a resource that has none has no effect.

Filtering

For simple equality, use the field name directly:
Repeating a field means any of these values, so the second example returns both phases and routes. Add an operator with a colon:
Both forms match either value. Use repeated parameters when a value itself contains a comma; repeated values are preserved verbatim. An empty :in list is rejected with 400. For anything with AND or OR in it, use filter:
Nested paths work in both styles, so computed.projectId and progress.status filter the same way a top-level column does. Joins let you resolve names in one request instead of following ids around.
The format is ?join:<table>:<fields>=<condition>. Use relation to refer to the joined table when you have not aliased it, or give it an alias with as and use that. Join on the column the resource actually exposes. A deliverable has no top-level projectId: the project it belongs to is computed.projectId, and only order nodes carry data.projectId. Joining a column that does not exist fails with a 500. Three things make joins more capable than they first look:
  • Pass several join: parameters in one request to join several tables.
  • A condition can reference another join’s alias, not just the base table, so you can hop across joined tables in a single request. Declare a join before any join that references it.
  • Conditions support JSONB paths such as d.data.stageId, and id fields are cast for you.
Chaining is what makes hours reportable. The timesheet attribution example walks through going from a timesheet row to its order, station, and phase in one call.
Joins to internal tables such as api_key are blocked.