# Filtering

List endpoints take filters as query parameters, one parameter per filter. Combine as many as you need; they are joined with AND unless you say OR.

```text
?filter[{field}]={operator}:{value}:{condition}
```

- **field** is the property to filter on, in lowercase. Nested properties use a dot: `productgroup.id`, `customer.name`.
- **operator** is one of the nine operators below.
- **value** is what to compare with. `null` and `notnull` take no value.
- **condition** is optional: `and` (default) or `or`. It joins this filter to the next one.

> The web version of this page has an interactive filter builder: https://ancon.dev/guides/filtering#playground

## Operators

| Operator | Matches when the field… | Example |
| --- | --- | --- |
| `has` | contains the value (text), or equals it (numbers, booleans, GUIDs) | `filter[name]=has:beef` |
| `in` | exactly equals one of a comma-separated list | `filter[producttype]=in:1,2,3` |
| `lt` / `lte` | is less than / less than or equal (numbers, dates) | `filter[created]=lt:2021-01-01 12:32` |
| `gt` / `gte` | is greater than / greater than or equal (numbers, dates) | `filter[created]=gte:2022-10-26 00:00` |
| `and` | has the bit flag set (enum flags) | `filter[orderformat]=and:8` |
| `null` | is null | `filter[deleted]=null` |
| `notnull` | is not null | `filter[deleted]=notnull` |

### has: search and single values

On text, `has` matches substrings without regard to case (a `LIKE '%value%'`), so it doubles as search. On numbers, booleans and GUIDs it is an exact match.

```text
?filter[name]=has:beef
?filter[name]=has:sall&filter[name]=has:beef
?filter[name]=has:sall:or&filter[name]=has:beef
?filter[producttype]=has:2
?filter[productgroup.id]=has:ca68cd1e-2a4c-4e81-8b30-75f4227467e3
?filter[hasvariants]=has:true
```

1. Names containing “beef”: *Beefburger*, *Steak beef 150g*.
2. Names containing both “sall” and “beef”: *Nice beef sallad*.
3. Names containing “sall” or “beef”: *Ceasar sallad*, *Beefburger*.
4. Product type exactly 2.
5. One product group, by id.
6. Products with variants.

### in: one of several exact values

`in` takes a comma-separated list and matches values that equal one of them exactly. Use it for several GUIDs or enum values on the same field; for a single value, `has` reads better.

```text
?filter[productgroup.id]=in:ca68cd1e-2a4c-4e81-8b30-75f4227467e3,51abeddc-bec7-4416-9aac-f791cbe0eb83
?filter[producttype]=in:1,2,3
?filter[name]=in:Baconburger 200g,Ceasar sallad
```

Don't put spaces after the commas: in `in:one, two` the second value is “ two”, with a leading space.

### lt, lte, gt, gte: ranges

Comparisons work on numbers and dates. Write dates as `2022-10-26 00:00` or in ISO 8601, `2022-10-26T00:00:00Z`, in UTC. Two filters on the same field make a range:

```text
?filter[created]=gte:2022-01-01 00:00&filter[created]=lt:2023-01-01 00:00
?filter[producttype]=gt:3
```

### and: enum flags

Some enums are bit flags: one value holds several options added together. `and` matches rows where the flag you pass is set. On orders, `orderformat` 8 is delivery, so this returns every delivery order, whatever other formats it also has:

```text
?filter[orderformat]=and:8
```

A value is the sum of its flags. 15 is 8 + 4 + 2 + 1, so it contains 8, 4, 2 and 1:

| Value | Flags set | Matches `and:32` | Matches `and:4` | Matches `and:2` |
| --- | --- | --- | --- | --- |
| 19 | 16 + 2 + 1 | no | no | yes |
| 37 | 32 + 4 + 1 | yes | yes | no |
| 15 | 8 + 4 + 2 + 1 | no | yes | yes |
| 66 | 64 + 2 | no | no | yes |

Pass one flag at a time.

### null and notnull

No value after the operator. Useful for soft-deleted entities:

```text
?filter[deleted]=null
?filter[deleted]=notnull
```

The first returns entities that aren't deleted, the second only the deleted ones.

## Combining filters

Filters are joined with AND by default: a row must match all of them. Add `:or` to a filter to join it to the next one with OR instead.

```text
?filter[tagid]=in:044ebf50-3436-4bb3-af6e-2f34df5afcbe&filter[productgroup.id]=in:ca68cd1e-2a4c-4e81-8b30-75f4227467e3
?filter[tagid]=in:044ebf50-3436-4bb3-af6e-2f34df5afcbe:or&filter[productgroup.id]=in:ca68cd1e-2a4c-4e81-8b30-75f4227467e3
```

The first returns products with that tag **and** in that group; the second, products with that tag **or** in that group. Use OR for things like “orders over 500 or refunded”, AND for “orders over 500 placed online”.

## Sending filters

- **cURL:** square brackets are glob characters in curl. Pass `-g` (`--globoff`) or encode them as `%5B` and `%5D`.
- **Spaces:** encode the space in `2022-10-26 00:00` as `%20`, or use the ISO 8601 form, which has none.
- **Libraries:** let your HTTP library build the query string. With `URLSearchParams` or `requests`, append one `filter[field]` pair per filter; repeated keys are fine.

```bash
curl -g "https://core-api.ancon.io/api/v1/Tenants/{tenantId}/Products?filter[name]=has:beef" \
  -H "Access-Token: $ANCON_TOKEN"
```

## Polling for changes

To pick up new and changed orders without re-reading the whole day, poll [Get order details list](https://ancon.dev/api/core/orders/get-order-details-list.md) with `filter[modified]=gte:<timestamp>`, using the time your previous poll started minus a minute. The endpoint page explains what changes `modified`.

## Which endpoints support filtering

Every list endpoint below accepts `filter[...]`. Where the fields are documented, the endpoint page lists them; elsewhere, filter on the fields you see in the response.

### Core API

- [AddOnGroups · Get add on group list](https://ancon.dev/api/core/add-on-groups/get-add-on-group-list.md): `GET /api/v1/Tenants/{tenantId}/AddOnGroups`
- [Checks · Get outlet check list V2](https://ancon.dev/api/core/checks/get-outlet-check-list-v2.md): `GET /api/v2/Outlets/{outletId}/Checks` (fields documented)
- [Discounts · Get discount list](https://ancon.dev/api/core/discounts/get-discount-list.md): `GET /api/v1/Tenants/{tenantId}/Discounts` (fields documented)
- [Fees · Get fee list](https://ancon.dev/api/core/fees/get-fee-list.md): `GET /api/v1/Tenants/{tenantId}/Fees` (fields documented)
- [Ingredients · Get ingredients list](https://ancon.dev/api/core/ingredients/get-ingredients-list.md): `GET /api/v1/Tenants/{tenantId}/Ingredients`
- [Invoices · Get invoice list](https://ancon.dev/api/core/invoices/get-invoice-list.md): `GET /api/v1/Outlets/{outletId}/Invoices` (fields documented)
- [MenuTrees · Get menu tree list](https://ancon.dev/api/core/menu-trees/get-menu-tree-list.md): `GET /api/v1/Tenants/{tenantId}/MenuTrees` (fields documented)
- [MetadataTemplates · Get metadata template list](https://ancon.dev/api/core/metadata-templates/get-metadata-template-list.md): `GET /api/v1/Tenants/{tenantId}/MetadataTemplate` (fields documented)
- [Orders · Get order details list](https://ancon.dev/api/core/orders/get-order-details-list.md): `GET /api/v1/Outlets/{outletId}/Orders/DetailedList` (fields documented)
- [OutletClientGroups · Get outlet client list.](https://ancon.dev/api/core/outlet-client-groups/get-outlet-client-list.md): `GET /api/v1/Outlets/{outletId}/ClientGroups`
- [OutletClients · Get client list](https://ancon.dev/api/core/outlet-clients/get-client-list.md): `GET /api/v1/Outlets/{outletId}/Clients` (fields documented)
- [ProductGroups · Get product group list](https://ancon.dev/api/core/product-groups/get-product-group-list.md): `GET /api/v1/Tenants/{tenantId}/ProductGroups` (fields documented)
- [Products · Get product list](https://ancon.dev/api/core/products/get-product-list.md): `GET /api/v1/Tenants/{tenantId}/Products` (fields documented)
- [SaleSources · Get Sale source list](https://ancon.dev/api/core/sale-sources/get-sale-source-list.md): `GET /api/v1/Tenants/{tenantId}/SaleSources` (fields documented)
- [Sections · Get section list](https://ancon.dev/api/core/sections/get-section-list.md): `GET /api/v1/Outlets/{outletId}/Sections`
- [Sections · Get section tables](https://ancon.dev/api/core/sections/get-section-tables.md): `GET /api/v1/Outlets/{outletId}/Sections/{sectionId}/Tables`
- [SettlementReports · Get Adyen settlement report list](https://ancon.dev/api/core/settlement-reports/get-adyen-settlement-report-list.md): `GET /api/v1/Outlets/{outletId}/SettlementReports` (fields documented)
- [Tables · Get table list](https://ancon.dev/api/core/tables/get-table-list.md): `GET /api/v1/Outlets/{outletId}/Tables` (fields documented)
- [Tags · Get tag list](https://ancon.dev/api/core/tags/get-tag-list.md): `GET /api/v1/Tenants/{tenantId}/Tags` (fields documented)
- [Taxes · Get tax list](https://ancon.dev/api/core/taxes/get-tax-list.md): `GET /api/v1/Tenants/{tenantId}/Taxes` (fields documented)
- [Tenants · Get outlet list](https://ancon.dev/api/core/tenants/get-outlet-list.md): `GET /api/v1/Tenants/Outlets` (fields documented)
- [Tickets · Get ticket details list](https://ancon.dev/api/core/tickets/get-ticket-details-list.md): `GET /api/v1/Outlets/{outletId}/Tickets/DetailedList` (fields documented)
- [UpsellGroups · Get upsell group list](https://ancon.dev/api/core/upsell-groups/get-upsell-group-list.md): `GET /api/v1/Tenants/{tenantId}/UpsellGroups`
- [WayOfPayments · Get way of payment list](https://ancon.dev/api/core/way-of-payments/get-way-of-payment-list.md): `GET /api/v1/Tenants/{tenantId}/WayOfPayments` (fields documented)
- [WayOfPayments · Get way of payment list](https://ancon.dev/api/core/way-of-payments/get-way-of-payment-list-v1.1.md): `GET /api/v1.1/Tenants/{tenantId}/WayOfPayments` (fields documented)
- [WayOfPayments · Get way of payment list](https://ancon.dev/api/core/way-of-payments/get-way-of-payment-list-v1.2.md): `GET /api/v1.2/Tenants/{tenantId}/WayOfPayments` (fields documented)

### Voucher API

- [VoucherBases · Get voucher basis list](https://ancon.dev/api/voucher/voucher-bases/get-voucher-basis-list.md): `GET /public/api/v1/Outlets/{outletId}/OrderVoucherBases` (fields documented)

### User API

- [Companies · Get tenant company list](https://ancon.dev/api/user/companies/get-tenant-company-list.md): `GET /public/api/v1/Tenants/{tenantId}/Companies` (fields documented)
- [Companies · Get tenant company members list](https://ancon.dev/api/user/companies/get-tenant-company-members-list.md): `GET /public/api/v1/Tenants/{tenantId}/Companies/{companyId}/Members`
- [Customers · Get Customer list by tenant](https://ancon.dev/api/user/customers/get-customer-list-by-tenant-v1.1.md): `GET /public/api/v1.1/Tenants/{tenantId}/Customers` (fields documented)
- [GuestGroups · Get guest group list by tenant id.](https://ancon.dev/api/user/guest-groups/get-guest-group-list-by-tenant-id.md): `GET /public/api/v1/Tenants/{tenantId}/GuestGroups` (fields documented)

The original filtering specification from 2022 is still available as a [PDF](https://ancon.dev/wp-content/uploads/2022/12/Filtering-2022-12-16.pdf).
