> ## Documentation Index
> Fetch the complete documentation index at: https://docs.komerza.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Filtering & Sorting

> Narrow down, order and page through list endpoints

List endpoints return everything by default. Four query parameters let you narrow that down:

| Parameter  | Does                         |
| ---------- | ---------------------------- |
| `filters`  | Which items come back        |
| `sorts`    | What order they come back in |
| `page`     | Which page you want          |
| `pageSize` | How many per page            |

You can use any of them on their own. Endpoints that support them show a **Filterable Fields** table on their reference page - that table is the list of field names you're allowed to use.

## Start with one filter

A filter is three parts stuck together: **field**, **operator**, **value**.

```bash theme={null}
GET /stores/{storeId}/orders?filters=status==pending
```

That's `status` (field) `==` (equals) `pending` (value). Add more by separating with commas - every condition has to match:

```bash theme={null}
# Pending orders, paid by Stripe
GET /stores/{storeId}/orders?filters=status==pending,gateway==stripe
```

Then order them, newest first, with `-` meaning descending:

```bash theme={null}
GET /stores/{storeId}/orders?filters=status==pending&sorts=-dateFrom
```

And take them 50 at a time:

```bash theme={null}
GET /stores/{storeId}/orders?filters=status==pending&sorts=-dateFrom&page=1&pageSize=50
```

That's the whole idea. The rest of this page is the detail.

<Warning>
  **A field name that isn't on the endpoint's list is ignored, not rejected.**
  You get a `200` with unfiltered results rather than an error. If a filter
  seems to do nothing, check the spelling against that endpoint's Filterable
  Fields table before looking anywhere else.
</Warning>

## Operators

**Comparing numbers and dates:**

| Operator | Meaning      | Example                |
| -------- | ------------ | ---------------------- |
| `==`     | Equals       | `status==pending`      |
| `!=`     | Not equals   | `gateway!=stripe`      |
| `>`      | Greater than | `rating>3`             |
| `<`      | Less than    | `rating<3`             |
| `>=`     | At least     | `dateFrom>=2026-01-01` |
| `<=`     | At most      | `dateTo<=2026-06-30`   |

**Matching text:**

| Operator | Meaning             | Example            |
| -------- | ------------------- | ------------------ |
| `@=`     | Contains            | `email@=gmail`     |
| `_=`     | Starts with         | `subject_=Refund`  |
| `_-=`    | Ends with           | `email_-=.com`     |
| `!@=`    | Does not contain    | `email!@=test`     |
| `!_=`    | Does not start with | `subject!_=[spam]` |
| `!_-=`   | Does not end with   | `email!_-=.ru`     |

Text matching is case-sensitive. Add a `*` to the end of any operator to ignore case:

```bash theme={null}
?filters=subject@=Premium     # matches "Premium", not "premium"
?filters=subject@=*premium    # matches both
```

Field names are always case-insensitive, so `dateFrom` and `datefrom` both work.

## Matching several values

Separate values with `|` to match any of them:

```bash theme={null}
# Orders paid by Stripe or PayPal
?filters=gateway==stripe|paypal
```

Put several **fields** in brackets to run the same test against each, matching if any one hits:

```bash theme={null}
# "urgent" in either the subject or the customer's email
?filters=(subject|customerEmail)@=urgent
```

## Values that need care

<AccordionGroup>
  <Accordion title="Enums: use the number, not the name">
    Enum fields take their numeric value: `visibility==0`, **not**
    `visibility==Public`. Passing the name matches nothing and returns an empty
    list. Every value is in [Enums & Constants](/api-reference/enums).

    Order status and gateway are the exceptions - they take the string name, as
    their field tables show.
  </Accordion>

  <Accordion title="Dates: ISO 8601">
    `dateFrom>=2026-01-01`, or with a time `dateFrom>=2026-01-01T09:30:00Z`.
  </Accordion>

  <Accordion title="Booleans: true or false">
    `isBestSeller==true`. Anything that isn't recognisably a boolean counts as
    `false`.
  </Accordion>

  <Accordion title="Commas, pipes and the word null">
    These mean something to the parser, so escape them with a backslash:

    | To match        | Write                |
    | --------------- | -------------------- |
    | `some,value`    | `field@=some\,value` |
    | `some\|value`   | `field@=some\|value` |
    | the text "null" | `field@=\null`       |

    Unescaped, `field@=some,value` is read as two separate filters.
  </Accordion>

  <Accordion title="Nulls and !=">
    `!=` leaves out rows where the field is empty. `gateway!=stripe` returns
    orders paid another way, but not orders with no gateway at all.
  </Accordion>
</AccordionGroup>

## Two shortcut filters

These aren't real fields, they're prepared questions. Pass `true`; passing `false` is the same as leaving them out.

| Field      | On       | Matches                                                     |
| ---------- | -------- | ----------------------------------------------------------- |
| `lowStock` | Products | Has a license-key variant and fewer than 5 in stock overall |
| `hasReply` | Reviews  | Already has a merchant reply                                |

## Sorting

One field, or several separated by commas. The first is the main order, the rest break ties. `-` means descending.

```bash theme={null}
?sorts=-dateFrom          # newest first
?sorts=status,-dateFrom   # by status, newest first inside each status
```

<Tip>
  When paging through results, sort by something nearly unique. If lots of rows
  share the same value, their order can shift between requests, so you may see
  the same row twice or miss one entirely.
</Tip>

## Paging

`page` starts at 1. `pageSize` defaults to **20** and is capped at **100** - ask for 500 and you get 100, with no error.

Responses tell you how many pages there are:

```json theme={null}
{
  "success": true,
  "pages": 5,
  "data": [
    // items
  ]
}
```

There's no total item count, so keep requesting pages until you reach `pages`.

<Note>
  A few endpoints use an older style, with the page in the path and a `limit` or
  `count` parameter:

  ```
  GET /stores/{storeId}/products/{productId}/variants/{variantId}/items/2?count=50
  ```

  Their reference pages show what they accept.
</Note>

## Recipes

Real requests you can paste and edit. `curl -G` with `--data-urlencode` matters here: filter values are full of `=`, `>`, `|` and `*`, and letting curl encode them avoids the request being mangled on the way out.

**Last 30 days of pending orders, newest first**

```bash theme={null}
curl -G "https://api.komerza.com/stores/{storeId}/orders" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "User-Agent: MyApp/1.0" \
  --data-urlencode "filters=status==pending,dateFrom>=2026-01-02" \
  --data-urlencode "sorts=-dateFrom"
```

**Unanswered 1-2 star reviews**

```bash theme={null}
curl -G "https://api.komerza.com/stores/{storeId}/reviews" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "User-Agent: MyApp/1.0" \
  --data-urlencode "filters=rating<3,hasReply==false" \
  --data-urlencode "sorts=-dateRangeFrom"
```

**Open tickets from one customer, oldest first**

```bash theme={null}
curl -G "https://api.komerza.com/stores/{storeId}/tickets" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "User-Agent: MyApp/1.0" \
  --data-urlencode "filters=status==0,customerEmail@=*buyer@example.com" \
  --data-urlencode "sorts=dateCreated" \
  --data-urlencode "pageSize=50"
```

**Public best-sellers running low on stock**

```bash theme={null}
curl -G "https://api.komerza.com/stores/{storeId}/products" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "User-Agent: MyApp/1.0" \
  --data-urlencode "filters=visibility==0,isBestSeller==true,lowStock==true"
```

## When it doesn't work

| What you see                      | What's wrong                                                                                   |
| --------------------------------- | ---------------------------------------------------------------------------------------------- |
| The filter changes nothing        | The field isn't on that endpoint's list, or is misspelled. Unknown fields are ignored silently |
| Empty list when filtering an enum | You passed the name instead of the number - use `visibility==0`                                |
| One value became two filters      | It contains a comma; escape it as `\,`                                                         |
| `pageSize=500` returned 100 items | 100 is the maximum, and larger values are quietly clamped                                      |
| Results shuffle between pages     | Equal sort values have no guaranteed order - add a tie-breaker field                           |
