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

# Постраничная навигация

> Получение списков через Living Security Public API с помощью конверта списка, параметра limit и непрозрачных курсоров page_token.

Каждая конечная точка списка `/v1` возвращает один и тот же конверт и использует
одинаковую постраничную навигацию: на основе токенов, только вперёд.

## Конверт списка

```json theme={null}
{
  "object": "list",
  "data": [
    {
      "id": "ent_8f3d2c1b0a9e",
      "object": "entity",
      "type": "person",
      "status": "active",
      "display_name": "Dana Rivera",
      "email": "dana.rivera@example.com",
      "created_at": "2025-11-03T09:21:44.000Z",
      "updated_at": "2026-07-28T16:02:10.000Z"
    }
  ],
  "next_page_token": "eyJvcmdhbml6YXRpb2...",
  "has_more": true
}
```

<ResponseField name="object" type="string">
  Всегда `"list"`.
</ResponseField>

<ResponseField name="data" type="array">
  Страница ресурсов в стабильном, детерминированном порядке.
</ResponseField>

<ResponseField name="next_page_token" type="string | null">
  Передайте это значение как `page_token` в следующем запросе, чтобы получить следующую страницу.
  `null`, если результатов больше нет.
</ResponseField>

<ResponseField name="has_more" type="boolean">
  Указывает, есть ли результаты за пределами текущей страницы.
</ResponseField>

## Параметры запроса

<ParamField query="limit" type="integer" default="25">
  Размер страницы. Целое число от 1 до 500. Значения вне этого диапазона приводят к возврату
  `400` с кодом `invalid_request`.
</ParamField>

<ParamField query="page_token" type="string">
  Значение `next_page_token` из предыдущего ответа. Для первой страницы не указывайте его.
</ParamField>

Параметры смещения, переход к предыдущей странице и точные значения общего
количества не поддерживаются. Чтобы подсчитать элементы коллекции, пройдите по ней полностью.

<Note>
  **Исключение:** `GET /v1/cohorts/{id}/members` возвращает не более 50 строк на
  страницу, даже если вы запрашиваете большее значение `limit`. `has_more` и
  `next_page_token` по-прежнему отражают фактический остаток Когорты.
</Note>

Выполните аутентификацию с помощью любого из двух вариантов учётных данных Basic — см.
[Краткое руководство](/ru/developers/v1/quickstart) (`apikey:secret` или `secret:`).
Укажите в `BASE` региональный хост организации — см.
[Регионы](/ru/developers/v1/regions).

## Обход списка

Загрузите `LIVING_SECURITY_API_KEY` из вашего менеджера секретов, как показано в кратком руководстве.
Для этого примера требуется `entity:read`; ключу, использованному в руководстве по первому запросу,
может потребоваться это дополнительное разрешение. Установите `requests` для Python или `jq` для примера
на shell. В обоих примерах зафиксирована версия, фильтры остаются неизменными, а перенаправления
отклоняются. Они останавливаются при ошибках и после 1000 страниц, а не сообщают о
частичном обходе как о завершённом. Тайм-аут чтения в Python ограничивает время бездействия, а не общую
длительность задания; задайте предельный срок выполнения задания в вашей интеграции.

Запрашивайте страницы, пока `next_page_token` не станет `null`. Для массового
чтения предпочтительнее использовать `limit=500`, чтобы охватить коллекцию за меньшее число запросов — см.
[ограничения частоты запросов](/ru/developers/v1/rate-limits).

<CodeGroup>
  ```bash cURL + jq theme={null}
  set -euo pipefail
  # US: BASE="https://api.us-east-1.livingsecurity.ai/v1"
  BASE="https://api.eu-west-1.livingsecurity.ai/v1"
  : "${LIVING_SECURITY_API_KEY:?Set LIVING_SECURITY_API_KEY}"
  TOKEN=""

  # Bound each run. Increase this page budget deliberately for larger collections.
  for ((page_number=1; page_number<=1000; page_number++)); do
    PARAMS=(--data-urlencode "limit=500" --data-urlencode "type=person")
    if [ -n "$TOKEN" ]; then
      PARAMS+=(--data-urlencode "page_token=$TOKEN")
    fi
    RESULT=$(curl --silent --show-error --fail-with-body \
      --connect-timeout 5 --max-time 30 --get \
      -u "apikey:$LIVING_SECURITY_API_KEY" \
      -H "Living-Security-Version: 2026-07-01" \
      --write-out '\n%{http_code}' "${PARAMS[@]}" "$BASE/entities")
    STATUS="${RESULT##*$'\n'}"
    PAGE="${RESULT%$'\n'*}"
    [ "$STATUS" = "200" ] || { echo "Unexpected HTTP status: $STATUS" >&2; exit 1; }
    jq -e 'has("next_page_token") and .object == "list" and (.data | type == "array") and
      ((.has_more == false and .next_page_token == null) or
       (.has_more == true and (.next_page_token | type == "string" and length > 0)))' \
      <<< "$PAGE" > /dev/null
    NEXT=$(jq -r '.next_page_token // empty' <<< "$PAGE")
    [ -z "$NEXT" ] || [ "$NEXT" != "$TOKEN" ] || { echo "Repeated page token" >&2; exit 1; }
    jq -r '.data[].id' <<< "$PAGE"
    [ -n "$NEXT" ] || exit 0
    TOKEN="$NEXT"
  done
  echo "Page budget exhausted; the collection is incomplete" >&2
  exit 1
  ```

  ```python Python theme={null}
  import os
  import requests

  # US: BASE = "https://api.us-east-1.livingsecurity.ai/v1"
  BASE = "https://api.eu-west-1.livingsecurity.ai/v1"
  AUTH = ("apikey", os.environ["LIVING_SECURITY_API_KEY"])

  def iter_entities(*, entity_type="person", max_pages=1000):
      if type(max_pages) is not int or max_pages < 1:
          raise ValueError("max_pages must be a positive integer")
      params = {"limit": 500, "type": entity_type}
      for _ in range(max_pages):
          response = requests.get(
              f"{BASE}/entities",
              auth=AUTH,
              params=params,
              headers={"Living-Security-Version": "2026-07-01"},
              timeout=(5, 30),
              allow_redirects=False,
          )
          try:
              response.raise_for_status()
              if response.status_code != 200:
                  raise RuntimeError("Unexpected response; check your regional API URL")
              page = response.json()
          finally:
              response.close()
          if (not isinstance(page, dict) or page.get("object") != "list"
                  or not isinstance(page.get("data"), list)
                  or type(page.get("has_more")) is not bool
                  or "next_page_token" not in page):
              raise RuntimeError("Invalid list response")
          token = page["next_page_token"]
          if ((page["has_more"] and (not isinstance(token, str) or not token))
                  or (not page["has_more"] and token is not None)):
              raise RuntimeError("Inconsistent list response")
          if token is not None and token == params.get("page_token"):
              raise RuntimeError("Repeated page token")
          yield from page["data"]
          if token is None:
              return
          params = {**params, "page_token": token}
      raise RuntimeError("Page budget exhausted; the collection is incomplete")

  if __name__ == "__main__":
      for entity in iter_entities():
          print(entity["id"])
  ```
</CodeGroup>

## Сохраняйте фильтры неизменными при постраничной навигации

Токены страниц — это непрозрачные подписанные курсоры. Каждый токен привязан к
организации, аутентифицированной служебной учётной записи, версии контракта и
точному набору фильтров запроса, в ответ на который он был выдан. Изменение любого из этих параметров в процессе обхода, например
добавление фильтра `type` на второй странице, приводит к возврату `400` с кодом
`invalid_request` для параметра `page_token`:

```json 400 Invalid request theme={null}
{
  "type": "https://docs.livingsecurity.com/developers/v1/errors/invalid_request",
  "title": "Invalid request",
  "status": 400,
  "detail": "page_token does not match this request's organization, credential, contract version, or filters.",
  "code": "invalid_request",
  "param": "page_token",
  "request_id": "req_...",
  "doc_url": "https://docs.livingsecurity.com/developers/v1/errors/invalid_request",
  "retryable": false
}
```

Чтобы изменить фильтры, начните новый обход с первой страницы.

<Tip>
  Обращайтесь с `page_token` как с непрозрачной строкой: передавайте её обратно в точности в том виде, в котором она получена.
  Не декодируйте токены, не храните их длительное время и не создавайте их самостоятельно — их внутренний формат
  может измениться в любой момент без предупреждения.
</Tip>

<Note>
  Конечные точки списков отклоняют неподдерживаемые ими параметры запроса, возвращая `400`
  `invalid_request` с указанием названия параметра. Поддерживаемые фильтры см. в справочнике по каждой
  конечной точке.
</Note>

## Обработка прерванной или изменяющейся коллекции

Эти примеры не выполняют автоматические повторные попытки при ошибках. Для `429` учитывайте `Retry-After`
и следуйте ограниченным [рекомендациям по повторным попыткам](/ru/developers/v1/rate-limits). При повторной попытке чтения сохраняйте
тот же регион, служебную учётную запись, версию, фильтры и токен последней успешно обработанной
страницы. Смена учётных данных не предоставляет доступа:
при каждом запросе текущие разрешения проверяются заново.

Чтение нескольких страниц не является моментальным снимком коллекции. Строки могут изменяться
в процессе обхода. Обрабатывайте ресурсы по их стабильному `id` и делайте последующие
операции записи идемпотентными; после перезапуска строка может быть возвращена повторно. Если токен отклонён,
изучите ошибку `page_token` и исправьте запрос, прежде чем начинать заново
с первой страницы. Не отбрасывайте молча ранее обработанные данные и не помечайте
прерванный экспорт как завершённый. Для получения точных итоговых значений в отчётах используйте соответствующий
ресурс отчётности, а не считайте обход живого списка моментальным снимком.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.