> ## 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.

# Phân trang

> Duyệt các danh sách của Living Security Public API bằng list envelope, tham số limit và các con trỏ page_token không trong suốt (opaque).

Mọi endpoint danh sách `/v1` đều trả về cùng một envelope và phân trang theo
cùng một cách: dựa trên token, chỉ tiến về phía trước.

## List envelope

```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">
  Luôn là `"list"`.
</ResponseField>

<ResponseField name="data" type="array">
  Trang tài nguyên, theo một thứ tự ổn định và xác định.
</ResponseField>

<ResponseField name="next_page_token" type="string | null">
  Truyền giá trị này làm `page_token` trong yêu cầu tiếp theo để lấy trang kế tiếp.
  Giá trị là `null` khi không còn kết quả nào nữa.
</ResponseField>

<ResponseField name="has_more" type="boolean">
  Cho biết còn kết quả nào ngoài trang này hay không.
</ResponseField>

## Tham số yêu cầu

<ParamField query="limit" type="integer" default="25">
  Kích thước trang. Một số nguyên từ 1 đến 500. Các giá trị nằm ngoài phạm vi này sẽ trả về
  `400` với mã `invalid_request`.
</ParamField>

<ParamField query="page_token" type="string">
  `next_page_token` từ phản hồi trước đó. Bỏ qua tham số này đối với trang đầu tiên.
</ParamField>

Không có tham số offset, không có khả năng duyệt ngược về trang trước và không có
tổng số chính xác. Để đếm một tập hợp, hãy duyệt toàn bộ tập hợp đó.

<Note>
  **Ngoại lệ:** `GET /v1/cohorts/{id}/members` chỉ trả về tối đa 50 hàng mỗi
  trang ngay cả khi bạn yêu cầu `limit` cao hơn. `has_more` và
  `next_page_token` vẫn phản ánh đúng phần còn lại của Nhóm.
</Note>

Hãy xác thực bằng một trong hai dạng thông tin xác thực Basic — xem
[Quickstart](/vi/developers/v1/quickstart) (`apikey:secret` hoặc `secret:`).
Đặt `BASE` thành máy chủ theo khu vực của tổ chức — xem
[Khu vực](/vi/developers/v1/regions).

## Duyệt một danh sách

Tải `LIVING_SECURITY_API_KEY` từ trình quản lý bí mật của quý vị như trong quickstart.
Ví dụ này cần `entity:read`; khóa được dùng cho hướng dẫn yêu cầu đầu tiên có thể
cần được cấp thêm quyền đó. Cài đặt `requests` cho Python hoặc `jq` cho ví dụ
shell. Cả hai ví dụ đều ghim một phiên bản, giữ nguyên các bộ lọc và từ chối
chuyển hướng. Các ví dụ dừng khi có lỗi và sau 1,000 trang thay vì báo cáo một
lượt duyệt dở dang là đã hoàn tất. Thời gian chờ đọc của Python chỉ giới hạn thời gian không hoạt động, không giới hạn tổng
thời lượng tác vụ; hãy áp dụng thời hạn cho tác vụ trong phần tích hợp của quý vị.

Hãy yêu cầu từng trang cho đến khi `next_page_token` là `null`. Nên ưu tiên `limit=500` cho các
lần đọc hàng loạt để bao phủ toàn bộ tập hợp với ít yêu cầu hơn — xem
[giới hạn tốc độ](/vi/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>

## Giữ nguyên bộ lọc trong khi phân trang

Page token là các con trỏ đã ký và không trong suốt (opaque). Mỗi token được gắn với
tổ chức, tài khoản dịch vụ đã xác thực, phiên bản hợp đồng và
đúng bộ lọc của yêu cầu đã tạo ra token đó. Việc thay đổi bất kỳ yếu tố nào trong số đó giữa chừng — chẳng hạn
thêm bộ lọc `type` ở trang thứ hai — sẽ trả về `400` với mã
`invalid_request` trên tham số `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
}
```

Để thay đổi bộ lọc, hãy bắt đầu lượt duyệt mới từ trang đầu tiên.

<Tip>
  Hãy coi `page_token` là một chuỗi không trong suốt: truyền lại đúng nguyên trạng như đã nhận.
  Không giải mã, không lưu trữ lâu dài và không tự tạo token — định dạng nội bộ của chúng
  có thể thay đổi bất kỳ lúc nào mà không cần báo trước.
</Tip>

<Note>
  Các endpoint danh sách từ chối những tham số truy vấn mà chúng không hỗ trợ bằng `400`
  `invalid_request` nêu tên tham số đó. Hãy xem tài liệu tham chiếu của từng endpoint để biết
  các bộ lọc được hỗ trợ.
</Note>

## Xử lý tập hợp bị gián đoạn hoặc thay đổi

Các ví dụ này không tự động thử lại khi gặp lỗi. Đối với `429`, hãy tuân thủ `Retry-After`
và làm theo [hướng dẫn thử lại](/vi/developers/v1/rate-limits) có giới hạn. Giữ nguyên
khu vực, tài khoản dịch vụ, phiên bản, bộ lọc và page token của trang được xử lý thành công gần nhất khi
thử lại một lần đọc. Việc thay đổi thông tin xác thực không cấp quyền truy cập:
mỗi yêu cầu vẫn kiểm tra các quyền hiện hành.

Việc đọc nhiều trang không phải là ảnh chụp nhanh (snapshot) của tập hợp. Các hàng có thể thay đổi trong khi
quý vị duyệt. Hãy xử lý tài nguyên theo `id` ổn định của chúng và làm cho các thao tác
ghi ở hạ nguồn có tính idempotent; việc khởi động lại có thể trả về một hàng lần nữa. Nếu một token bị từ chối,
hãy kiểm tra lỗi `page_token` và sửa yêu cầu trước khi bắt đầu lại từ
trang đầu tiên. Không âm thầm loại bỏ dữ liệu đã xử lý trước đó và không đánh dấu một
lần xuất bị gián đoạn là đã hoàn tất. Để có tổng số báo cáo chính xác, hãy dùng
tài nguyên báo cáo phù hợp thay vì coi một lượt duyệt danh sách trực tiếp là ảnh chụp nhanh.


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