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

# Introduction

> Authenticate with a bearer token and page, filter, and sort results from the Vori REST API at api.vori.com.

The Vori REST API lets grocers pull and manage data like store products programmatically. All requests go to `https://api.vori.com`.

## Authentication

<Note>
  If you or the integration consuming this API only need to read data, create a user with the **Read-only** role at the banner level and authenticate with that account instead of a full-access one. This avoids errors from write attempts you didn't anticipate needing, and limits the blast radius if the credentials are mishandled.
</Note>

Every API request must include a bearer token in the `Authorization` header. Get one by exchanging your Vori login email and password for a token via Google's Identity Toolkit:

<Steps>
  <Step title="Request a token">
    Send a `POST` request to:

    ```
    https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=AIzaSyBetj86Hkh--jwfI3gjF_m7qig9cVLlHdM
    ```

    with a JSON body containing your Vori credentials:

    ```json theme={null}
    {
        "email": "{{authEmail}}",
        "password": "{{authPassword}}",
        "returnSecureToken": true
    }
    ```

    Example using `curl`:

    ```bash theme={null}
    curl -X POST \
      "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=AIzaSyBetj86Hkh--jwfI3gjF_m7qig9cVLlHdM" \
      -H "Content-Type: application/json" \
      -d '{
        "email": "your-email@example.com",
        "password": "your-password",
        "returnSecureToken": true
      }'
    ```
  </Step>

  <Step title="Extract the token">
    The response is a JSON object that includes an `idToken` field. This is your bearer token — use it in the `Authorization` header of subsequent requests to `https://api.vori.com`:

    ```
    Authorization: Bearer <idToken>
    ```
  </Step>
</Steps>

<Note>
  Tokens expire after an hour, so you'll need to repeat this exchange periodically rather than hardcoding a token indefinitely.
</Note>

### Use the refresh token instead of re-authenticating

The `verifyPassword` response also includes a `refreshToken` field. Instead of resending your email and password every hour, exchange that refresh token for a new `idToken` via Google's secure token endpoint:

```
https://securetoken.googleapis.com/v1/token?key=AIzaSyBetj86Hkh--jwfI3gjF_m7qig9cVLlHdM
```

with a JSON body:

```json theme={null}
{
    "grant_type": "refresh_token",
    "refresh_token": "{{refreshToken}}"
}
```

Example using `curl`:

```bash theme={null}
curl -X POST \
  "https://securetoken.googleapis.com/v1/token?key=AIzaSyBetj86Hkh--jwfI3gjF_m7qig9cVLlHdM" \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "refresh_token",
    "refresh_token": "your-refresh-token"
  }'
```

The response contains a new `id_token` (your bearer token) and a new `refresh_token` — store the new refresh token for the next exchange, since the old one may be rotated.

<Note>
  This uses Google Identity Toolkit's standard secure token exchange rather than a Vori-specific endpoint. Verify the response field names (`id_token`, `refresh_token`) against what you receive, since Google's REST API is documented outside of Vori's own docs.
</Note>

## Pagination, filtering, and sorting

List endpoints like `/v1/store-products` accept a `state` query parameter: a URL-encoded JSON object that controls which rows come back, how they're ordered, and how many are returned at once. Within that JSON object, only `startRow`, `endRow`, `filterModel`, and `sortModel` are supported — all other properties are rejected. This is separate from other query parameters an endpoint may support.

```json theme={null}
{
    "startRow": 0,
    "endRow": 50,
    "filterModel": { "<field>": <filter> },
    "sortModel": [{ "colId": "<field>", "sort": "asc" }]
}
```

**Pagination** — `startRow` and `endRow` select which slice of results to return: `startRow` defaults to `0`, and `endRow` defaults to `startRow` plus 10. You can request at most 1000 rows at a time (`endRow - startRow <= 1000`). The response's `rowCount` field always reflects the total number of matching rows across the entire result set, not just the rows returned in this request.

**Sorting** — `sortModel` orders results by any filterable field, plus `id`. If you don't specify a `sortModel`, results are sorted by `created_at` descending (e.g., newest first).

**Filtering** — `filterModel` is an object keyed by field name, where each value describes how to match that field:

| Filter  | Example                                                                                   | `type` values                                                                                                            |
| ------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Set     | `{"filterType": "set", "values": ["1", "2"]}`                                             | `in`, `notIn`                                                                                                            |
| Boolean | `{"filterType": "boolean", "values": [true]}`                                             | `in`, `notIn`                                                                                                            |
| Text    | `{"filterType": "text", "type": "contains", "filter": "cola"}`                            | `equals`, `notEqual`, `contains`, `notContains`, `startsWith`, `endsWith`, `blank`, `notBlank`                           |
| Number  | `{"filterType": "number", "type": "greaterThan", "filter": 10}`                           | `equals`, `notEqual`, `lessThan`, `lessThanOrEqual`, `greaterThan`, `greaterThanOrEqual`, `inRange`, `blank`, `notBlank` |
| Date    | `{"filterType": "date", "type": "greaterThan", "dateFrom": "2026-01-01", "dateTo": null}` | `equals`, `notEqual`, `lessThan`, `greaterThan`, `inRange`, `blank`, `notBlank`                                          |

`type` is optional for set and boolean filters and defaults to `in`. Boolean fields also accept set filters, and date fields also accept `"filterType": "datetime"`. Which fields are filterable, and which filter types each supports, varies by endpoint.

**Example:** active products in a specific store, sorted by price, 50 at a time:

```bash theme={null}
curl -G "https://api.vori.com/v1/store-products" \
  -H "Authorization: Bearer <idToken>" \
  --data-urlencode 'state={"startRow":0,"endRow":50,"filterModel":{"store_id":{"filterType":"set","values":["123"]},"active":{"filterType":"boolean","values":[true]}},"sortModel":[{"colId":"base_retail_price","sort":"desc"}]}'
```

<Note>
  Filtering by an unsupported field, or sending a `state` shape outside of `startRow`, `endRow`, `filterModel`, and `sortModel`, returns a 400 error naming the unsupported field.
</Note>


## Related topics

- [View & Edit Loyalty Member Data](/customer-marketing/loyalty-and-rewards/view-edit-loyalty-member-data.md)
- [View Loyalty Program Reporting Metrics](/customer-marketing/loyalty-and-rewards/view-loyalty-program-reporting-metrics.md)
- [How to log into Vusion Link with a QR code generated from a different phone](/electronic-shelf-labels-esls/how-to-log-into-vusion-link-with-a-qr-code-generated-from-a-different-phone.md)
