Skip to content

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:

EndpointBehaviour
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:

EndpointNot 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).

ParameterCasingValid values
severityPascalCaseCritical, High, Medium, Low
statusPascalCaseOpen, Acknowledged, Dismissed
typelowercase phraseconsecutive 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_since is 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 returns 400 VALIDATION_ERROR.
  • Every endpoint above that supports updated_since returns an updated_at field on each list item (ISO 8601 UTC), always populated. Use the newest updated_at you've seen as the updated_since value on your next poll. On GET /v1/timesheet_idle_times/, updated_at is on each nested timesheet object.

Known limitations — read before building a sync loop on this:

  • No tombstones for hard-deleted records. members and clients are hard-deleted on removal (no soft-delete row retained) — a removed member or client never appears via updated_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_since alone 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 LeavePolicy is deleted, its dependent leave applications are deactivated in a way that does not bump their updated_at. In practice this has no visible effect on this endpoint, since those rows are already excluded by the existing is_active = true filter regardless of updated_at.
  • Task assignee sync: the task_new_members junction table has no updated_at, so there is no org-wide incremental endpoint for task assignments. Poll GET /v1/tasks/?updated_since=... for task-field changes, then GET /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:

  1. user_id + team_id together → 400 on time_activity_report and manual_timesheet_reports.
  2. user_id + team_id together → allowed on clockinout (AND semantics).
  3. All other combinations of supported filters are additive (AND semantics), including updated_since with any other filter on the endpoints listed above.