> For the complete documentation index, see [llms.txt](https://docs.thewalletcrew.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.thewalletcrew.io/developers-guides/integration-guides/wallet/filter-and-query-operators.md).

# Filter and query operators

## Filter and query operators

Several pass-related endpoints accept a `filter` array that narrows results by pass data field. Use filters together with pagination to retrieve exactly the set of passes you need.

Filtering is supported on these endpoints:

* [`GET /api/{tenantId}/passes`](https://docs.thewalletcrew.io/api-reference/pass-management/pass) — list passes
* [`POST /api/{tenantId}/passes/pushUpdate`](https://docs.thewalletcrew.io/api-reference/pass-management/pass) — trigger a push update on a filtered set
* [`POST /api/{tenantId}/passes/export/token`](https://docs.thewalletcrew.io/api-reference/pass-management/pass) — export a filtered set

## Retrieve passes with optional filtering, sorting, and pagination.

> \*\*Authorization:\*\* Requires \`Pass.Read\` scope.\
> \
> \*\*Filtering:\*\* By identifiers (e.g., \`identifiers.email\`), metadata fields (e.g., \`metadata.loyaltyTier\`), pass type, or installation status (apple, google).\
> \
> \*\*Sorting:\*\* By id, passType, creationDate, lastUpdateDate, apple, google, or any identifier/metadata field.\
> \
> \*\*Pagination:\*\* Zero-based \`pageIndex\` and \`pageSize\`. Total count in \`x-pagination-total\` header.\
> \
> \*\*Use Cases:\*\* Search passes by customer attributes; monitor installation status; filter by loyalty tier or campaign flag.\
> \
> \*\*Example — list loyalty passes, page 0:\*\*\
> \`\`\`\
> GET /api/{tenantId}/passes?pageIndex=0\&pageSize=20\
> &#x20;   \&filter\[0].field=passType\&filter\[0].operator=equals\&filter\[0].value=loyalty\
> &#x20;   \&sortBy\[0].field=creationDate\&sortBy\[0].direction=DESC\
> \`\`\`\
> \*\*Example — passes installed on Apple Wallet:\*\*\
> \`\`\`\
> GET /api/{tenantId}/passes?filter\[0].field=installationStatus\&filter\[0].operator=contains\&filter\[0].value=apple\
> \`\`\`

````json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Pass"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["ScopedAuthorizeRequirement"]},{"apiKey":["ScopedAuthorizeRequirement"]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"FilterModel":{"type":"object","properties":{"field":{"type":"string","description":"Field to filter by (identifiers.*, metadata.*, passType, installationStatus)."},"operator":{"description":"Comparison operator to apply.","$ref":"#/components/schemas/FilterModelOperator"},"value":{"type":["null","array"],"items":{"type":"string"},"description":"Filter values. Single-element for most operators; multiple elements for `in` and `notIn`.\nThe query parameter name is `value` (repeated for multiple values).\n<example>\nSingle value (equals, contains, startsWith, …):\n```\nGET /passes?filter[0].field=passType&filter[0].operator=equals&filter[0].value=boarding\n```\nMultiple values (in / notIn):\n```\nGET /passes?filter[0].field=passType&filter[0].operator=in&filter[0].value=boarding&filter[0].value=loyalty\n```\nMultiple filters combined:\n```\nGET /passes?filter[0].field=passType&filter[0].operator=equals&filter[0].value=boarding\n           &filter[1].field=identifiers.customerId&filter[1].operator=startsWith&filter[1].value=CUST-\n```</example>"}},"additionalProperties":false,"description":"Filter specification for pass queries."},"FilterModelOperator":{"enum":["Contains","StartsWith","EndsWith","Equals","IsEmpty","IsNotEmpty","NotEquals","In","NotIn"],"type":"string","description":"Filter operators for pass list queries."},"SortModel":{"type":"object","properties":{"field":{"type":"string","description":"Field to sort by."},"direction":{"description":"Sort direction (ASC or DESC).","$ref":"#/components/schemas/SortModelDirection"}},"additionalProperties":false,"description":"Sort specification for pass queries."},"SortModelDirection":{"enum":[0,1],"type":"integer","description":"Sort direction for pass list queries.","format":"int32"},"Pass":{"type":"object","properties":{"id":{"type":"string","description":"Platform-assigned unique pass identifier. This is a random alphanumeric string generated at creation time and cannot be set by callers (e.g., `xK9mP2nQr7sT`)."},"secret":{"type":"string","description":"Platform-generated opaque token used internally to authenticate pass delivery. Read-only; treat as confidential."},"creationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"Timestamp when the pass was created by the platform. Read-only. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"lastUpdateDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"Timestamp of the most recent update. Equals `CreationDate` when no update has occurred. Read-only. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"passType":{"type":"string","description":"Type of pass, matching a file in the tenant `server/passes/` configuration."},"identifiers":{"type":"object","additionalProperties":{"type":"string"},"description":"External identifiers keyed by system name (e.g., `shopify.customerId`)."},"metadata":{"type":"object","additionalProperties":{"type":"null"},"description":"Computed metadata fields managed by the platform."},"additionalData":{"type":"object","additionalProperties":{"type":"null"},"description":"Arbitrary key/value data persisted with the pass."},"providers":{"type":"object","additionalProperties":{},"description":"Installation details keyed by provider name (e.g., `apple`, `google`). Populated by the platform. Read-only."}},"additionalProperties":false,"description":"Represents a pass returned by the API."}}},"paths":{"/api/{tenantId}/passes":{"get":{"tags":["Pass"],"summary":"Retrieve passes with optional filtering, sorting, and pagination.","description":"**Authorization:** Requires `Pass.Read` scope.\n\n**Filtering:** By identifiers (e.g., `identifiers.email`), metadata fields (e.g., `metadata.loyaltyTier`), pass type, or installation status (apple, google).\n\n**Sorting:** By id, passType, creationDate, lastUpdateDate, apple, google, or any identifier/metadata field.\n\n**Pagination:** Zero-based `pageIndex` and `pageSize`. Total count in `x-pagination-total` header.\n\n**Use Cases:** Search passes by customer attributes; monitor installation status; filter by loyalty tier or campaign flag.\n\n**Example — list loyalty passes, page 0:**\n```\nGET /api/{tenantId}/passes?pageIndex=0&pageSize=20\n    &filter[0].field=passType&filter[0].operator=equals&filter[0].value=loyalty\n    &sortBy[0].field=creationDate&sortBy[0].direction=DESC\n```\n**Example — passes installed on Apple Wallet:**\n```\nGET /api/{tenantId}/passes?filter[0].field=installationStatus&filter[0].operator=contains&filter[0].value=apple\n```","parameters":[{"name":"pageIndex","in":"query","description":"Zero-based page index.","schema":{"type":"integer","format":"int32","default":0}},{"name":"pageSize","in":"query","description":"Number of passes per page.","schema":{"type":"integer","format":"int32","default":20}},{"name":"filter","in":"query","description":"Optional filters applied to identifiers, metadata, pass type, or installation status.","schema":{"type":"array","items":{"description":"Filter specification for pass queries.","$ref":"#/components/schemas/FilterModel"}}},{"name":"sortBy","in":"query","description":"Optional sort descriptors. When absent, results are unordered.","schema":{"type":"array","items":{"description":"Sort specification for pass queries.","$ref":"#/components/schemas/SortModel"}}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Paged list of passes returned successfully.","content":{"text/plain":{"schema":{"type":"array","items":{"description":"Represents a pass returned by the API.","$ref":"#/components/schemas/Pass"}}},"application/json":{"schema":{"type":"array","items":{"description":"Represents a pass returned by the API.","$ref":"#/components/schemas/Pass"}}},"text/json":{"schema":{"type":"array","items":{"description":"Represents a pass returned by the API.","$ref":"#/components/schemas/Pass"}}}}}}}}}}
````

## Push update for passes matching the filter.

> \*\*Authorization:\*\* Requires \`Pass.Write\` scope.\
> \
> \*\*Filtering:\*\* Same as GetPasses�identifiers, metadata, pass type, installation status.\
> \
> \*\*Async:\*\* Updates are queued. 200 response means scheduled, not complete. Monitor via statistics endpoint.\
> \
> \*\*Data Merge:\*\* Merged into each matching pass. Set \`UpdateMetadata=true\` for recomputation (slower). Adjust \`Throughput\` for concurrency.\
> \
> \*\*Bulk Operations:\*\* Ideal for campaigns, loyalty updates, seasonal offers. Use \`CorrelationId\` for tracking.\
> \
> \*\*Use Cases:\*\* Campaign push; loyalty tier changes; offer refresh; bulk metadata updates.\
> \
> \*\*Example — push a seasonal offer to all loyalty passes:\*\*\
> \`\`\`\
> POST /api/{tenantId}/passes/pushUpdate\
> &#x20;   ?filter\[0].field=passType\&filter\[0].operator=equals\&filter\[0].value=loyalty\
> &#x20;           \
> {\
> &#x20; "additionalData": { "offer": "summer2025", "discount": "20%" },\
> &#x20; "options": {\
> &#x20;   "updateMetadata": true,\
> &#x20;   "throughput": 12,\
> &#x20;   "correlationId": "campaign-summer-2025"\
> &#x20; }\
> }\
> \`\`\`

````json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Pass"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["ScopedAuthorizeRequirement"]},{"apiKey":["ScopedAuthorizeRequirement"]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"FilterModel":{"type":"object","properties":{"field":{"type":"string","description":"Field to filter by (identifiers.*, metadata.*, passType, installationStatus)."},"operator":{"description":"Comparison operator to apply.","$ref":"#/components/schemas/FilterModelOperator"},"value":{"type":["null","array"],"items":{"type":"string"},"description":"Filter values. Single-element for most operators; multiple elements for `in` and `notIn`.\nThe query parameter name is `value` (repeated for multiple values).\n<example>\nSingle value (equals, contains, startsWith, …):\n```\nGET /passes?filter[0].field=passType&filter[0].operator=equals&filter[0].value=boarding\n```\nMultiple values (in / notIn):\n```\nGET /passes?filter[0].field=passType&filter[0].operator=in&filter[0].value=boarding&filter[0].value=loyalty\n```\nMultiple filters combined:\n```\nGET /passes?filter[0].field=passType&filter[0].operator=equals&filter[0].value=boarding\n           &filter[1].field=identifiers.customerId&filter[1].operator=startsWith&filter[1].value=CUST-\n```</example>"}},"additionalProperties":false,"description":"Filter specification for pass queries."},"FilterModelOperator":{"enum":["Contains","StartsWith","EndsWith","Equals","IsEmpty","IsNotEmpty","NotEquals","In","NotIn"],"type":"string","description":"Filter operators for pass list queries."},"MultipleUpdatePassData":{"type":"object","allOf":[{"$ref":"#/components/schemas/MultipleUpdatePassDataOptionsUpdatePassData"}],"additionalProperties":false,"description":"Data payload for multi-pass (bulk) update operations."},"MultipleUpdatePassDataOptionsUpdatePassData":{"type":"object","properties":{"identifiers":{"type":"object","additionalProperties":{"type":"string"},"description":"External identifiers of the customer for this pass. Keys must not start with `id.`; common examples are `y2.customerId` or `shopify.customerId`.\nUse an empty value to remove an identifier. Leave the collection empty to keep existing identifiers unchanged."},"additionalData":{"type":["null","object"],"additionalProperties":{"type":"null"},"description":"Arbitrary data to persist with the pass (for example, loyalty tier, store code, or campaign flags)."},"passType":{"type":["null","string"],"description":"Optional pass type to convert the pass to."},"updateMetadata":{"type":"boolean","description":"Specifies if passes metadata should be updated. Updating metadata is time consuming and could be avoided for notification only push update","default":false,"deprecated":true},"options":{"description":"Options for multi-pass (bulk) update operations.","$ref":"#/components/schemas/MultipleUpdatePassDataOptions"}},"additionalProperties":false},"MultipleUpdatePassDataOptions":{"type":"object","allOf":[{"$ref":"#/components/schemas/UpdatePassDataOptions"}],"properties":{"throughput":{"type":"number","description":"Desired throughput (passes per second) when scheduling updates. Default is 12. Throughput is best-effort and can vary. Values below 1 slowly pace updates.","format":"float"}},"additionalProperties":false,"description":"Options for multi-pass (bulk) update operations."},"UpdatePassDataOptions":{"type":"object","properties":{"updateMetadata":{"type":"boolean","description":"When true, recompute and persist pass metadata. Updating metadata is slower and is usually unnecessary for notification-only updates."},"correlationId":{"type":["null","string"],"description":"Groups related updates under the same correlationId. Useful for batch updates (for example nightly jobs) to make retries and logs traceable."}},"additionalProperties":false,"description":"Options used when updating passes."},"PushUpdateResult":{"type":"object","properties":{"passCount":{"type":"integer","description":"Count of passes scheduled for update.","format":"int32"}},"additionalProperties":false,"description":"Result returned after scheduling a bulk push update operation."}}},"paths":{"/api/{tenantId}/passes/pushUpdate":{"post":{"tags":["Pass"],"summary":"Push update for passes matching the filter.","description":"**Authorization:** Requires `Pass.Write` scope.\n\n**Filtering:** Same as GetPasses�identifiers, metadata, pass type, installation status.\n\n**Async:** Updates are queued. 200 response means scheduled, not complete. Monitor via statistics endpoint.\n\n**Data Merge:** Merged into each matching pass. Set `UpdateMetadata=true` for recomputation (slower). Adjust `Throughput` for concurrency.\n\n**Bulk Operations:** Ideal for campaigns, loyalty updates, seasonal offers. Use `CorrelationId` for tracking.\n\n**Use Cases:** Campaign push; loyalty tier changes; offer refresh; bulk metadata updates.\n\n**Example — push a seasonal offer to all loyalty passes:**\n```\nPOST /api/{tenantId}/passes/pushUpdate\n    ?filter[0].field=passType&filter[0].operator=equals&filter[0].value=loyalty\n            \n{\n  \"additionalData\": { \"offer\": \"summer2025\", \"discount\": \"20%\" },\n  \"options\": {\n    \"updateMetadata\": true,\n    \"throughput\": 12,\n    \"correlationId\": \"campaign-summer-2025\"\n  }\n}\n```","parameters":[{"name":"filter","in":"query","description":"Optional filters applied to select passes.","schema":{"type":"array","items":{"description":"Filter specification for pass queries.","$ref":"#/components/schemas/FilterModel"}}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Data to merge into each pass and options controlling metadata refresh and throughput.","content":{"application/json":{"schema":{"description":"Data payload for multi-pass (bulk) update operations.","$ref":"#/components/schemas/MultipleUpdatePassData"}},"text/json":{"schema":{"description":"Data payload for multi-pass (bulk) update operations.","$ref":"#/components/schemas/MultipleUpdatePassData"}},"application/*+json":{"schema":{"description":"Data payload for multi-pass (bulk) update operations.","$ref":"#/components/schemas/MultipleUpdatePassData"}}}},"responses":{"200":{"description":"Passes scheduled for update; returns the count in the response body.","content":{"text/plain":{"schema":{"description":"Result returned after scheduling a bulk push update operation.","$ref":"#/components/schemas/PushUpdateResult"}},"application/json":{"schema":{"description":"Result returned after scheduling a bulk push update operation.","$ref":"#/components/schemas/PushUpdateResult"}},"text/json":{"schema":{"description":"Result returned after scheduling a bulk push update operation.","$ref":"#/components/schemas/PushUpdateResult"}}}}}}}}}
````

## Create a time-limited download token for exporting passes as Excel.

> Returns a short-lived signed URL (valid 5 minutes) that can be used to download\
> the xlsx without an Authorization header. Filters are embedded in the token.

````json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"PassExport"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["ScopedAuthorizeRequirement"]},{"apiKey":["ScopedAuthorizeRequirement"]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"FilterModel":{"type":"object","properties":{"field":{"type":"string","description":"Field to filter by (identifiers.*, metadata.*, passType, installationStatus)."},"operator":{"description":"Comparison operator to apply.","$ref":"#/components/schemas/FilterModelOperator"},"value":{"type":["null","array"],"items":{"type":"string"},"description":"Filter values. Single-element for most operators; multiple elements for `in` and `notIn`.\nThe query parameter name is `value` (repeated for multiple values).\n<example>\nSingle value (equals, contains, startsWith, …):\n```\nGET /passes?filter[0].field=passType&filter[0].operator=equals&filter[0].value=boarding\n```\nMultiple values (in / notIn):\n```\nGET /passes?filter[0].field=passType&filter[0].operator=in&filter[0].value=boarding&filter[0].value=loyalty\n```\nMultiple filters combined:\n```\nGET /passes?filter[0].field=passType&filter[0].operator=equals&filter[0].value=boarding\n           &filter[1].field=identifiers.customerId&filter[1].operator=startsWith&filter[1].value=CUST-\n```</example>"}},"additionalProperties":false,"description":"Filter specification for pass queries."},"FilterModelOperator":{"enum":["Contains","StartsWith","EndsWith","Equals","IsEmpty","IsNotEmpty","NotEquals","In","NotIn"],"type":"string","description":"Filter operators for pass list queries."},"ExportTokenResponse":{"type":"object","properties":{"downloadUrl":{"type":"string"}},"additionalProperties":false}}},"paths":{"/api/{tenantId}/passes/export/token":{"post":{"tags":["PassExport"],"summary":"Create a time-limited download token for exporting passes as Excel.","description":"Returns a short-lived signed URL (valid 5 minutes) that can be used to download\nthe xlsx without an Authorization header. Filters are embedded in the token.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Filters to apply to the export (same semantics as the passes list API).","content":{"application/json":{"schema":{"type":"array","items":{"description":"Filter specification for pass queries.","$ref":"#/components/schemas/FilterModel"}}},"text/json":{"schema":{"type":"array","items":{"description":"Filter specification for pass queries.","$ref":"#/components/schemas/FilterModel"}}},"application/*+json":{"schema":{"type":"array","items":{"description":"Filter specification for pass queries.","$ref":"#/components/schemas/FilterModel"}}}}},"responses":{"200":{"description":"Download token created successfully.","content":{"text/plain":{"schema":{"$ref":"#/components/schemas/ExportTokenResponse"}},"application/json":{"schema":{"$ref":"#/components/schemas/ExportTokenResponse"}},"text/json":{"schema":{"$ref":"#/components/schemas/ExportTokenResponse"}}}}}}}}}
````

### Filter request shape

Each filter condition is a `FilterModel` object with three fields:

| Field      | Type      | Description                                                                 |
| ---------- | --------- | --------------------------------------------------------------------------- |
| `field`    | string    | The pass data field to filter on (e.g. `passType`, `loyaltyTier`)           |
| `operator` | string    | The comparison operator (see below)                                         |
| `value`    | string\[] | One or more values to compare against. Omit for `isEmpty` and `isNotEmpty`. |

Filters are passed as indexed query parameters:

```
filter[0].field=passType&filter[0].operator=equals&filter[0].value=boarding
```

Multiple conditions are combined with logical AND:

```
filter[0].field=passType&filter[0].operator=equals&filter[0].value=boarding
&filter[1].field=loyaltyTier&filter[1].operator=isNotEmpty
```

### Operators

#### String operators

These operators compare the field value against a single string.

| Operator     | Behaviour                               |
| ------------ | --------------------------------------- |
| `equals`     | Exact match                             |
| `notEquals`  | Does not match                          |
| `contains`   | Field contains the value as a substring |
| `startsWith` | Field starts with the value             |
| `endsWith`   | Field ends with the value               |

Example — passes with a loyalty tier that contains "gold":

```
filter[0].field=loyaltyTier&filter[0].operator=contains&filter[0].value=gold
```

#### Presence operators

These operators check whether a field has a value. No `value` parameter is required.

| Operator     | Behaviour                  |
| ------------ | -------------------------- |
| `isEmpty`    | Field is absent or null    |
| `isNotEmpty` | Field has a non-null value |

Example — passes where `externalId` is not set:

```
filter[0].field=externalId&filter[0].operator=isEmpty
```

#### Set operators

These operators match against a list of values. Repeat the `value` parameter to supply multiple entries.

| Operator | Behaviour                                    |
| -------- | -------------------------------------------- |
| `in`     | Field matches any value in the list          |
| `notIn`  | Field matches none of the values in the list |

Example — passes of type `boarding` or `loyalty`:

```
filter[0].field=passType&filter[0].operator=in&filter[0].value=boarding&filter[0].value=loyalty
```

{% hint style="info" %}
`in` and `notIn` are available via the API only. The back-office passes table does not expose these operators in its filter controls.
{% endhint %}

### Pagination

The passes list endpoint uses offset-based pagination.

| Parameter   | Type    | Default | Description                |
| ----------- | ------- | ------- | -------------------------- |
| `pageIndex` | integer | `0`     | Zero-based page index      |
| `pageSize`  | integer | `20`    | Number of results per page |

The response includes an `x-pagination-total` header with the total number of matching passes across all pages.

To retrieve all passes in batches, increment `pageIndex` until the full count is consumed.

### Combining filters and pagination

Filters and pagination parameters are independent query parameters and can be combined freely.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.thewalletcrew.io/developers-guides/integration-guides/wallet/filter-and-query-operators.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
