*REST API › Guides*

# Filtering and sorting

> Most lists accept `sort` and the `created*` and `updated*` time filters, many accept list-valued filters such as `statuses`, and some accept `q` for free-text search. The examples use the orders list.

## Sort

Send [`sort`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-sort) with up to three comma-separated fields; repeating a field is a 400. Lists such as incidents, kits, services, payments, invoice payments and treasury accounts take only one field (see the parameter description). A leading `-` sorts that field in descending order: `sort=-startDate,orderNumber`. Each list names its sortable fields in the description of the parameter; for orders they are `createdAt`, `updatedAt`, `startDate`, `endDate`, `orderNumber`, `grossPrice`, `rentalDays` and `itemsCount`, and the default is `-createdAt`.

- A field that is not in the list of sortable fields is rejected with 400 [`validation_error`](https://docs.filmo.io/rest-api/errors/validation_error), and the error names it.
- Ties are broken by `id`, so the order is stable across pages.
- A cursor is bound to the sort it was issued with: keep `sort` the same on every page. See [Pagination](https://docs.filmo.io/rest-api/guides/pagination).
- [`GET /api/v1/availability/unavailabilities`](https://docs.filmo.io/rest-api/endpoint-reference/availability/list-the-unavailabilities-of-a-resource) does not accept `sort`: it follows the date cursor of its source. The lists that are not paginated (see [Pagination](https://docs.filmo.io/rest-api/guides/pagination)) are not sortable either.
- If you sort by a field that changes while you page, such as `updatedAt`, a record can be repeated or skipped.

The sortable fields of every list:

| Operation | Sort by |
| --- | --- |
| `GET /api/v1/contacts` | createdAt, updatedAt, name, contactType, role, status, email |
| `GET /api/v1/items` | createdAt, updatedAt, name, sku, salePrice, depositValue, serializedItemsCount, collectionName |
| `GET /api/v1/orders` | createdAt, updatedAt, startDate, endDate, orderNumber, grossPrice, rentalDays, itemsCount |
| `GET /api/v1/orders/{orderId}/lines` | position, itemName, quantity, startDate, endDate, rentalDays, unitPrice, linePrice, status, resourceType, transactionType |
| `GET /api/v1/invoices` | createdAt, updatedAt, invoiceNumber, invoiceDate, dueDate, subTotal, grossPrice |
| `GET /api/v1/quotes` | createdAt, updatedAt, quoteNumber, issueDate, startDate, endDate, subTotal, grossPrice, operativeStatus |
| `GET /api/v1/projects` | createdAt, updatedAt, name, projectNumber, startDate, endDate, status |
| `GET /api/v1/invoices/{invoiceId}/payments` | date, amount, createdAt, updatedAt |
| `GET /api/v1/invoices/{invoiceId}/lines` | position, itemName, quantity, startDate, endDate, rentalDays, unitPrice, linePrice, resourceType, transactionType |
| `GET /api/v1/quotes/{quoteId}/lines` | position, itemName, quantity, startDate, endDate, rentalDays, unitPrice, linePrice, resourceType, transactionType |
| `GET /api/v1/serialized-items` | createdAt, updatedAt, code, serialNumber, status, condition, itemName |
| `GET /api/v1/spaces` | createdAt, updatedAt, name, internalReference, isWarehouse, building, floor |
| `GET /api/v1/repair-tickets` | createdAt, updatedAt, date, status, total |
| `GET /api/v1/vehicles` | createdAt, updatedAt, name, registrationNumber, status, seats, payloadKg |
| `GET /api/v1/staff` | createdAt, updatedAt, fullName, staffType |
| `GET /api/v1/subrents` | createdAt, updatedAt, startDate, endDate, pickupDate, returnDate, status, type, quantity |
| `GET /api/v1/purchase-orders` | createdAt, updatedAt, poNumber, poDate, status, type, grossPrice, netPrice, isDraft |
| `GET /api/v1/contracts` | createdAt, updatedAt, contractNumber, contractDate, startDate, endDate, status, signedAt, grossPrice |
| `GET /api/v1/kits` | name, createdAt, updatedAt |
| `GET /api/v1/collections` | path, name, order, depth, itemCount, createdAt, updatedAt |
| `GET /api/v1/services` | name, serviceType, defaultQty, createdAt, updatedAt |
| `GET /api/v1/internal-services` | startDate, endDate, title, status, createdAt, updatedAt |
| `GET /api/v1/incidents` | createdAt, updatedAt, dueDate, identifier, title, status, priority |
| `GET /api/v1/notes` | createdAt, updatedAt |
| `GET /api/v1/tasks` | createdAt, updatedAt, startDate, endDate, title, isCompleted, type |
| `GET /api/v1/crm/opportunities` | createdAt, updatedAt, name, identifier, status, amount, expectedCloseDate |
| `GET /api/v1/supplier-invoices` | createdAt, updatedAt, invoiceDate, dueDate |
| `GET /api/v1/treasury/accounts` | name, type, status, currency, createdAt, updatedAt |
| `GET /api/v1/treasury/payments` | date, amount, createdAt, updatedAt |
| `GET /api/v1/credit-notes` | createdAt, updatedAt, issueDate, grossPrice |
| `GET /api/v1/proformas` | createdAt, updatedAt, issueDate, proformaNumber |
| `GET /api/v1/workflows` | name, isActive, triggerType, createdAt, updatedAt |
| `GET /api/v1/payment-methods` | name, type, createdAt, updatedAt |
| `GET /api/v1/users` | name, email, status, createdAt, updatedAt |

## Time filters

Most lists filter by the time a record was created or last updated. The two families share five suffixes:

| Suffix | The record's time is… | Created | Updated |
| --- | --- | --- | --- |
| On | on that date | `createdOn` | `updatedOn` |
| After | after that date | `createdAfter` | `updatedAfter` |
| Before | before that date | `createdBefore` | `updatedBefore` |
| From | on or after that date | `createdFrom` | `updatedFrom` |
| To | on or before that date | `createdTo` | `updatedTo` |

Each value is a bare day (`YYYY-MM-DD`) or an ISO 8601 instant with an offset. A bare day is read in the business timezone of the account ([`timezone`](https://docs.filmo.io/rest-api/objects/account#response-timezone)): `From` starts at the beginning of that day, `To` ends at the end of it, `After` means after the end of that day, `Before` means before its start, and `On` covers the whole day. An instant with an offset is applied as given, except with `On`, which covers the whole business day that contains it.

The operators are strict where their names say so: `After` is `>`, `Before` is `<`, `From` is `>=` and `To` is `<=`, compared with millisecond precision. For a bare day, `From` and `Before` use 00:00:00.000 of that day and `To` and `After` use 23:59:59.999, so `updatedAfter=2026-10-09` excludes the whole of 9 October. `From` together with `Before` gives a half-open window, `[from, before)`, whose edges never overlap with the next window. That is the shape [Syncing data](https://docs.filmo.io/rest-api/guides/syncing-data) builds on. Not every list has these filters: [`GET /api/v1/tags`](https://docs.filmo.io/rest-api/endpoint-reference/tags/list-tags), [`GET /api/v1/task-types`](https://docs.filmo.io/rest-api/endpoint-reference/task-types/list-task-types), [`GET /api/v1/crm/pipelines`](https://docs.filmo.io/rest-api/endpoint-reference/crm/list-pipelines), [`GET /api/v1/custom-fields`](https://docs.filmo.io/rest-api/endpoint-reference/custom-fields/list-custom-field-definitions) and [`GET /api/v1/project-types`](https://docs.filmo.io/rest-api/endpoint-reference/projects/list-project-types) reject any `created*` or `updated*` filter with 400; [`GET /api/v1/kits`](https://docs.filmo.io/rest-api/endpoint-reference/kits/list-kits), [`GET /api/v1/services`](https://docs.filmo.io/rest-api/endpoint-reference/services/list-services), [`GET /api/v1/internal-services`](https://docs.filmo.io/rest-api/endpoint-reference/internal-services/list-internal-services), [`GET /api/v1/crm/opportunities`](https://docs.filmo.io/rest-api/endpoint-reference/crm/list-opportunities), [`GET /api/v1/orders/{orderId}/lines`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-the-lines-of-an-order), [`GET /api/v1/invoices/{invoiceId}/lines`](https://docs.filmo.io/rest-api/endpoint-reference/invoices/list-the-lines-of-an-invoice), [`GET /api/v1/quotes/{quoteId}/lines`](https://docs.filmo.io/rest-api/endpoint-reference/quotes/list-the-lines-of-a-quote) and [`GET /api/v1/invoices/{invoiceId}/payments`](https://docs.filmo.io/rest-api/endpoint-reference/invoices/list-the-payments-of-an-invoice) have no `updated*` filters, and [`GET /api/v1/incidents`](https://docs.filmo.io/rest-api/endpoint-reference/incidents/list-incidents) has only `created*`; [`GET /api/v1/payment-methods`](https://docs.filmo.io/rest-api/endpoint-reference/treasury/list-payment-methods), [`GET /api/v1/treasury/payments`](https://docs.filmo.io/rest-api/endpoint-reference/treasury/list-treasury-payments) and [`GET /api/v1/treasury/accounts`](https://docs.filmo.io/rest-api/endpoint-reference/treasury/list-treasury-accounts) can be sorted by `updatedAt` but have no `updated*` filters. Check the parameters of the operation. Lists add their own date filters too; orders also filter on when they start and end (for example [`startsFrom`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-startsFrom) and [`endsBefore`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-endsBefore)) and on a rental period that overlaps a window ([`overlapsFrom`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-overlapsFrom) and [`overlapsTo`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-overlapsTo)).

## Filters that take several values

Some filters have a list-valued twin. [`statuses`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-statuses), [`contactIds`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-contactIds) and [`projectIds`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-projectIds) take up to 50 values and return the records that match any of them. Repeat the parameter or separate the values with commas: `statuses=reserved,prepared` and `statuses=reserved&statuses=prepared` are the same filter.

- The single-value filter is a different parameter: [`status`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-status) takes one operational bucket (`draft`, `reserved`, `prepared`, `in_use`, `returned`, `completed` or `cancelled`), [`contactId`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-contactId) one contact.
- More than 50 values is a 400. To filter by a larger set, split it into batches of 50 and merge the results.

## Free-text search

Where a list offers [`q`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-q), it is a free-text search whose description says what it matches. On orders it searches the order number, the project name or alias and the external reference, with 1 to 200 characters.

## An example

Orders that are reserved or prepared, updated since 1 October and before 8 October, whose text matches `acme`, soonest start first and the biggest first among equal starts:

*Filter, search and sort the orders*

**cURL**

```bash
curl -sS --fail-with-body -G "${FILMO_BASE_URL:-https://app.filmo.io}/api/v1/orders" \
  -H "Authorization: Bearer $FILMO_API_KEY" \
  --data-urlencode "statuses=reserved,prepared" \
  --data-urlencode "updatedFrom=2026-10-01" \
  --data-urlencode "updatedBefore=2026-10-08T00:00:00+02:00" \
  --data-urlencode "q=acme" \
  --data-urlencode "sort=startDate,-grossPrice" \
  --data-urlencode "limit=50" |
  jq -r '.data[] | [.orderNumber, .status, .startDate, .grossPrice] | @tsv'
```

**Node**

```javascript
const base = process.env.FILMO_BASE_URL ?? "https://app.filmo.io";
const url = new URL("/api/v1/orders", base);
url.searchParams.set("statuses", "reserved,prepared"); // or repeat: statuses=reserved&statuses=prepared
url.searchParams.set("updatedFrom", "2026-10-01"); // on or after the start of that day (account timezone)
url.searchParams.set("updatedBefore", "2026-10-08T00:00:00+02:00"); // before this instant
url.searchParams.set("q", "acme");
url.searchParams.set("sort", "startDate,-grossPrice"); // soonest start first, then the biggest order
url.searchParams.set("limit", "50");

const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.FILMO_API_KEY}` } });
if (!res.ok) throw new Error(res.status + " " + (await res.text()));
const { data } = await res.json();
for (const order of data) console.log(order.orderNumber, order.status, order.startDate, order.grossPrice);
```

**Python**

```python
import os
import requests

base = os.environ.get("FILMO_BASE_URL", "https://app.filmo.io")
params = {
    "statuses": ["reserved", "prepared"],  # sent as repeated parameters; a comma-separated string works too
    "updatedFrom": "2026-10-01",  # on or after the start of that day (account timezone)
    "updatedBefore": "2026-10-08T00:00:00+02:00",  # before this instant
    "q": "acme",
    "sort": "startDate,-grossPrice",  # soonest start first, then the biggest order
    "limit": 50,
}
res = requests.get(
    base + "/api/v1/orders",
    headers={"Authorization": f"Bearer {os.environ['FILMO_API_KEY']}"},
    params=params,
    timeout=30,
)
res.raise_for_status()
for order in res.json()["data"]:
    print(order["orderNumber"], order["status"], order["startDate"], order["grossPrice"])
```

Combine filters with the loop in [Pagination](https://docs.filmo.io/rest-api/guides/pagination): keep the same filters, `sort` and `limit` on every page.

---

Canonical URL: https://docs.filmo.io/rest-api/guides/filtering-and-sorting  
Last updated: 2026-10-09