Filters Guide
Overview
Most list endpoints accept optional filter parameters to narrow results. This guide covers which filters are available on which endpoints, how they interact, and the specific behavioral rules that differ from what you might expect from a standard REST API.
user_id and team_id — Mutex Rules (Report Endpoints)
On two of the three report endpoints, user_id and team_id are mutually exclusive:
| Endpoint | Behaviour |
|---|---|
GET /v1/time_activity_reports/ | user_id and team_id together → 400 VALIDATION_ERROR |
GET /v1/manual_timesheet_reports/ | user_id and team_id together → 400 VALIDATION_ERROR |
GET /v1/clockinouts/ | user_id and team_id together → allowed (AND semantics) |
The mutex check fires before UUID validation — passing both with a zero UUID is still rejected.
Rationale (ADR-011): The time activity and manual timesheet reports discover their user set from Timetrack data; combining user and team filters would require a three-way intersection that is complex and rarely useful. The clockinout report builds a full member roster up front and can apply both filters naturally in a single query.
is_active — Client and Project Filtering
Clients
Client list and retrieve endpoints do not filter by is_active. Inactive clients appear in GET /v1/clients/ and resolve normally in GET /v1/clients/{client_id}/. The is_active field is always present in responses so you can filter locally.
404 on a client endpoint unambiguously means the client does not exist in this organization — not that it is inactive (ADR-004).
Exception: GET /v1/clients/{client_id}/projects/ does filter by is_active = true. An inactive client returns 404 from this endpoint because the client–project relationship is no longer meaningful for an inactive client.
Projects
Project endpoints do not filter by is_active. All projects (active and inactive) appear in lists and resolve in retrieve/sub-resource endpoints. The is_active field is present in all project responses (ADR-006).
404 on a project endpoint means the project does not exist in this organization.
Members
Member endpoints do filter by active membership. GET /v1/members/ returns only active members. GET /v1/teams/{team_id}/members/ returns only members who are both active team members and hold an active organization membership.
project_id Filter
Available on: GET /v1/timesheet_idle_times/, GET /v1/time_activity_reports/, GET /v1/manual_timesheet_reports/, GET /v1/app_reports/, GET /v1/url_reports/, GET /v1/screenshots/.
Narrows results to data belonging to a specific project. On report endpoints, project existence is validated against the organization before querying Timetrack data — an invalid or foreign project_id returns 400 VALIDATION_ERROR.
team_id Filter
Available on: GET /v1/timesheet_idle_times/, GET /v1/time_activity_reports/, GET /v1/manual_timesheet_reports/, GET /v1/clockinouts/, GET /v1/app_reports/, GET /v1/url_reports/.
Narrows results to members of the specified team.
Behaviour when team is not found:
| Endpoint | Not found response |
|---|---|
GET /v1/timesheet_idle_times/ | 404 RESOURCE_NOT_FOUND |
GET /v1/time_activity_reports/ | 404 RESOURCE_NOT_FOUND |
GET /v1/manual_timesheet_reports/ | 404 RESOURCE_NOT_FOUND |
GET /v1/app_reports/ | 404 RESOURCE_NOT_FOUND |
GET /v1/url_reports/ | 404 RESOURCE_NOT_FOUND |
GET /v1/clockinouts/ | 200 with empty results |
When the team exists but has no active members, all endpoints above return 200 with empty results.
The clockinout endpoint also returns 200 (not 404) when the team_id UUID does not resolve to a team — it treats that as an empty roster filter. See ADR-011 and ADR-038.
GET /v1/teams/{team_id}/members/ uses a path parameter (not a filter); unknown teams return 404.
Zero UUID Sentinel
Some client systems pass 00000000-0000-0000-0000-000000000000 to mean "no filter". All UUID filter parameters accept the zero UUID and treat it as if the parameter was omitted entirely.
This matches Django's convention used in the Apploye web application.
created_by Filter
Available on: GET /v1/projects/, GET /v1/clients/{client_id}/projects/, GET /v1/members/{user_id}/projects/.
Narrows projects to those created by the specified user. The zero UUID sentinel applies — passing the zero UUID is equivalent to omitting the filter.
status Filter (Invoices)
Available on: GET /v1/invoices/.
Filters invoices by status string. Valid values: draft, sent, paid, partial (all lowercase — case-sensitive). An unrecognized status returns zero results (no 400).
Suspicious activity enum filters
Available on: GET /v1/suspicious_activity_logs/ (requires user_id).
| Parameter | Casing | Valid values |
|---|---|---|
severity | PascalCase | Critical, High, Medium, Low |
status | PascalCase | Open, Acknowledged, Dismissed |
type | lowercase phrase | consecutive injection, partial injection |
Invalid values return 400 VALIDATION_ERROR. Do not lowercase these filters.
Leave application status filter
Available on: GET /v1/leave_applications/.
Valid values: pending, accepted, rejected (lowercase).
Date Filters on Payroll Endpoints
GET /v1/members/{user_id}/hourly_payment_logs/ and GET /v1/members/{user_id}/one_time_payment_logs/ accept optional start_date / end_date parameters that filter on paid_for_date and paid_at respectively (both YYYY-MM-DD). These are open-ended: you may supply one without the other.
client_id Filter (Invoices)
Available on: GET /v1/invoices/.
Narrows invoices by client. Foreign or invalid UUIDs return zero results (no 404).
updated_since — Incremental Sync Filter
Available on: GET /v1/members/, GET /v1/clients/, GET /v1/projects/, GET /v1/clients/{client_id}/projects/, GET /v1/members/{user_id}/projects/, GET /v1/tasks/, GET /v1/projects/{project_id}/tasks/, GET /v1/project_memberships/, GET /v1/invoices/, GET /v1/leave_applications/, GET /v1/suspicious_activity_logs/, GET /v1/timesheet_idle_times/.
Returns only records whose updated_at is on or after the given ISO 8601 UTC datetime (e.g. 2026-01-01T00:00:00Z). Designed for polling clients that want to fetch only what changed since their last successful poll, instead of re-pulling the full list every cycle.
Behavior:
- Omitted: every endpoint's response — including sort order, filtering, and pagination — is unchanged from its documented default behavior. Passing no
updated_sinceis always safe for existing integrations. - Present: results are sorted by
(updated_at, id)ascending instead of the endpoint's default sort. Combine with the endpoint's other filters using AND semantics, same as any other filter (see "Combining Filters" below). An unparseable value returns400 VALIDATION_ERROR. - Every endpoint above that supports
updated_sincereturns anupdated_atfield on each list item (ISO 8601 UTC), always populated. Use the newestupdated_atyou've seen as theupdated_sincevalue on your next poll. OnGET /v1/timesheet_idle_times/,updated_atis on each nested timesheet object.
Known limitations — read before building a sync loop on this:
- No tombstones for hard-deleted records.
membersandclientsare hard-deleted on removal (no soft-delete row retained) — a removed member or client never appears viaupdated_since, because there's no row left to return. If you need to detect removals, you must diff the full ID set against your local mirror periodically;updated_sincealone cannot tell you something is gone. - `suspicious_activity_logs` requires `user_id` always but supports the same dual-mode filter as timesheet idle time:
(start_date + end_date) OR updated_since(or both). - `leave_applications` has one known blind spot: when a
LeavePolicyis deleted, its dependent leave applications are deactivated in a way that does not bump theirupdated_at. In practice this has no visible effect on this endpoint, since those rows are already excluded by the existingis_active = truefilter regardless ofupdated_at. - Task assignee sync: the
task_new_membersjunction table has noupdated_at, so there is no org-wide incremental endpoint for task assignments. PollGET /v1/tasks/?updated_since=...for task-field changes, thenGET /v1/tasks/{task_id}/members/per changed task. Assignment-only changes on otherwise-unchanged tasks are not captured by task deltas alone — revisit when the junction table gains a timestamp column (see ADR-031).
Combining Filters
Most endpoints allow all supported filters to be combined freely. The key restrictions are:
user_id+team_idtogether →400ontime_activity_reportandmanual_timesheet_reports.user_id+team_idtogether → allowed onclockinout(AND semantics).- All other combinations of supported filters are additive (AND semantics), including
updated_sincewith any other filter on the endpoints listed above.
