Filtering
Filters narrow a listing server-side, so you fetch the rows you want rather than paging the whole set and discarding most of it. Which filters an endpoint accepts is listed on that endpoint's own page; the syntax below is the same everywhere.
filters_applied
A response that was narrowed reports what narrowed it:
{
"data": [ "..." ],
"page": 1,
"page_size": 100,
"total_items": 12,
"filters_applied": {
"country": "Israel",
"active": false
}
}
This exists because a filtered page is otherwise ambiguous. Seeing 12 rows out of 2000, you cannot tell whether your filter was applied, silently ignored, or read as something you did not mean. Three rules make it answer that:
The values are the ones the server parsed, not the strings you sent. ?active=yes
comes back as "active": true. If you passed a bare date, you see the exact instant it
was widened to. This is how you confirm your input was understood.
The key is absent when nothing was filtered — not present and empty. An absent key means "this is the unfiltered set", which an empty object would leave ambiguous.
A parameter that narrowed nothing is not listed. Paging parameters are not filters
and never appear, and neither do the projection parameters (fields, populate), which
change which columns come back rather than which rows.
Asserting on filters_applied in your own tests is the cheapest way to catch a
misspelled filter name: the server tells you exactly what it acted on.
Exact and partial text
Text filters match the whole value, case-insensitively:
?country=Israel
Each one has a _pattern twin that matches a substring instead:
?country_pattern=isra
Free-text search
Where an endpoint offers search, it matches across several columns at once — for
employees, the first name, last name and email:
?search=sarah
On the template search endpoint the term is required, and matches the template name and description.
Booleans
true, 1 and yes all mean true; false, 0 and no all mean false. Case does not
matter. Anything else is a 400 rather than a silent default:
{ "error": "'active' must be true or false" }
Dates
Date filters use a bracketed operator — lt, lte, gt or gte:
?created_date[gte]=2026-01-01
?created_date[lt]=2026-02-01T09:30:00Z
A full timestamp is used exactly as given. A bare YYYY-MM-DD is widened to cover the
whole day: an upper bound (lt, lte) takes the end of that day and a lower bound
takes the start, so ?created_date[lte]=2026-01-01 includes everything that happened on
the 1st rather than only the instant of midnight. The filters_applied echo reports the
instant actually used, so you can always see which way it was read.
A value that is not a date, or a date that does not exist such as 2026-02-30, is a
400:
{ "error": "'created_date[gte]' is not a valid date or datetime: 2026-02-30" }
An operator the endpoint does not support is a 400, rather than being ignored:
{ "error": "'between' is not a supported operator for created_date; use one of gt, gte, lt, lte" }
Multiple values
Where a filter accepts several values, both spellings work and mean the same thing:
?level=1&level=2
?level=1,2
Combining them
Filters combine with AND, and apply before paging — so total_items counts the filtered
set, not the whole table:
curl "https://apigw.goninjio.com/api/api_gateway/3.0.0/customer/employees/\
?country=Israel&department=Sales&active=true&page_size=50" \
-H "apiKey: YOUR_API_KEY_HERE"
Deleted records
Deleting a simulation, a campaign or a mass delivery does not remove it — it marks the record deleted, and the web UI stops showing it. The API does the same: records the customer deleted are not returned.
That applies to the listings: simulations, campaigns and tenant mass deliveries.
Fetching one record by id is not filtered. If you hold an id — from a webhook, from
your own reporting table — you get that record whether or not it has since been deleted,
with no parameter needed. Simulations returned that way carry "is_deleted": true, so
you can tell. Excluding deleted records is about answering "what exists", which is a
question only a listing asks.
The lists a detail response carries are filtered, though. You did not name those rows
by id — you are being told what the record contains, which is a listing. So deleted
simulations are left out of a campaign's simulations_ids, out of a training campaign's
emotions, and out of a template's template_simulations. There is no opt-in for these:
the customer's own screens have no way to show them either. The emotions case is the
one to know about, because it is an aggregate — an emotion contributed by a deleted
simulation is not something you could filter out on your side.
If you genuinely need the full history — a reporting job reconciling against its own records, an audit export — opt in:
?include_deleted=true
true, 1 and yes opt in. Anything else leaves deleted records hidden, including
?include_deleted=maybe and an empty value: a malformed flag fails closed rather than
leaking. To check the server understood you, read the echo — when you send the
parameter, the parsed value comes back in filters_applied:
"filters_applied": { "include_deleted": true }
Simulations returned this way carry "is_deleted": true, so an opted-in caller can still
tell them apart.
The parameter is only meaningful on those listings. A by-id endpoint ignores it, because it never filtered in the first place.
Records that are deactivated rather than deleted are a different thing and are still
returned: an inactive employee, a disabled sending domain. Both already carry a flag of
their own, and employees have an explicit active filter.