# Three filters returned the wrong rows

A literal `%` in a filter value dropped the filter. `series_code` matched any series containing the code. Sorting customers ignored the documented field names. **Only `series_code` changes a response you may rely on today.**

Aug 22, 2026 · Breaking

Three cases where a filter or an ordering you sent came back `200` having done something other than what you asked. A literal `%` in a value dropped the filter entirely; `series_code` matched any series *containing* the code you sent; and sorting customers only understood undocumented field names. All three now either work as documented or tell you they cannot.

Only `series_code` changes a response you may be relying on today. The other two turn a wrong `200` into a `400` that names the problem.

## What else changed

- **A literal `%` in a filter value dropped that filter.** `?search=100%OFF` or `?status=DRAFT%` came back `200` with the collection unfiltered — not an error, just every row. This is what any client that builds its query string without percent-encoding the values produces, so it did not take a strange request to hit it: asking for your drafts returned every invoice on the account. A value whose percent-encoding is malformed is now named and rejected. Encode the value and it works as before — a literal `%` is `%25`.
- **That rejection joins the date already announced.** It travels on exactly the mechanism from [the strict query parameters change](/changelog#strict-query-parameters-and-rate-limits): the canonical routes reject it today, the flat aliases answer `200` with a `BeeL-Warning` header naming the parameter until **2026-09-03**, then reject too. No new date to track, and the header tells you which of your requests are affected before then.
- **`series_code` now means one series, not any series containing it.** It was matching as a substring, so `?series_code=R` also returned everything filed under `STR` — and there was no way at all to ask for a single series. It now matches the series code exactly, still ignoring case. **This is the one change that can quietly return fewer rows than yesterday**: if you were relying on the substring behaviour to sweep several series at once, use `search`, which is documented as a partial match across the invoice number, the recipient and the series code.
- **Sorting customers now takes the field names we document.** `sort_by=city`, `province`, `active` and `legal_name` were accepted with `200` and then ignored, falling back to the default order — including `legal_name`, the example in our own reference. They now sort. The full vocabulary is `legal_name`, `nif`, `email`, `phone`, `city`, `province`, `active`, `created_at`; anything else is answered with `400` listing those. If you were sending the Spanish field names that happened to work, they are the ones that stop being accepted — the names above are the same ones the filters on that operation already use.
- **Paging through customers no longer repeats or skips anyone.** The order had no tie-breaker, so customers sharing a legal name — the field sorted by default — could appear on two pages or on none. Ordering is now total, and a full walk of the collection returns each customer exactly once.

## Endpoints

- `GET /v1/companies/{company_id}/invoices` — `series_code` matches one series exactly; a malformed `%` in any filter value is named and rejected
- `GET /v1/companies/{company_id}/customers` — `sort_by` takes the documented field names and pages without repeating rows

## Where to go next

- [List invoices](/invoices/listCompanyInvoices)
- [List customers](/customers/listCompanyCustomers)

---

Full OpenAPI spec: https://docs.beel.es/api/openapi