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

# Paginasi

> Telusuri daftar pada Living Security Public API menggunakan envelope daftar, parameter limit, dan kursor opak page_token.

Setiap endpoint daftar `/v1` mengembalikan envelope yang sama dan melakukan paginasi
dengan cara yang sama: berbasis token dan hanya ke arah maju.

## Envelope daftar

```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">
  Selalu `"list"`.
</ResponseField>

<ResponseField name="data" type="array">
  Halaman berisi resource, dalam urutan yang stabil dan deterministik.
</ResponseField>

<ResponseField name="next_page_token" type="string | null">
  Teruskan nilai ini sebagai `page_token` pada permintaan berikutnya untuk mengambil halaman selanjutnya.
  `null` jika tidak ada hasil lagi.
</ResponseField>

<ResponseField name="has_more" type="boolean">
  Menunjukkan apakah masih ada hasil di luar halaman ini.
</ResponseField>

## Parameter permintaan

<ParamField query="limit" type="integer" default="25">
  Ukuran halaman. Bilangan bulat antara 1 dan 500. Nilai di luar rentang tersebut mengembalikan
  `400` dengan kode `invalid_request`.
</ParamField>

<ParamField query="page_token" type="string">
  `next_page_token` dari respons sebelumnya. Lewati parameter ini untuk halaman pertama.
</ParamField>

Tidak ada parameter offset, tidak ada penelusuran ke halaman sebelumnya, dan tidak ada
jumlah total yang tepat. Untuk menghitung sebuah koleksi, telusuri koleksi tersebut.

<Note>
  **Pengecualian:** `GET /v1/cohorts/{id}/members` mengembalikan paling banyak 50 baris per
  halaman, meskipun Anda meminta `limit` yang lebih tinggi. `has_more` dan
  `next_page_token` tetap mencerminkan sisa Kelompok yang sebenarnya.
</Note>

Lakukan autentikasi dengan salah satu bentuk kredensial Basic — lihat
[Quickstart](/id/developers/v1/quickstart) (`apikey:secret` atau `secret:`).
Atur `BASE` ke host regional organisasi — lihat
[Regions](/id/developers/v1/regions).

## Menelusuri daftar

Muat `LIVING_SECURITY_API_KEY` dari secret manager Anda seperti pada quickstart.
Contoh ini memerlukan `entity:read`; kunci yang digunakan untuk panduan permintaan pertama
mungkin memerlukan izin tambahan tersebut. Instal `requests` untuk Python atau `jq` untuk contoh
shell. Kedua contoh mengunci versi, menjaga filter tetap tidak berubah, dan menolak
pengalihan. Keduanya berhenti saat terjadi error dan setelah 1.000 halaman, alih-alih melaporkan
penelusuran yang parsial sebagai lengkap. Read timeout pada Python membatasi waktu tidak aktif, bukan total
durasi tugas; terapkan batas waktu tugas pada integrasi Anda.

Minta halaman hingga `next_page_token` bernilai `null`. Utamakan `limit=500` untuk pembacaan
massal agar koleksi tercakup dalam lebih sedikit permintaan — lihat
[rate limits](/id/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>

## Jaga filter tetap stabil selama paginasi

Token halaman adalah kursor opak yang ditandatangani. Setiap token terikat pada
organisasi, service account yang diautentikasi, versi kontrak, dan
kumpulan filter yang persis sama dari permintaan yang menghasilkannya. Mengubah salah satunya di tengah penelusuran — misalnya,
menambahkan filter `type` pada halaman kedua — akan mengembalikan `400` dengan kode
`invalid_request` pada parameter `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
}
```

Untuk mengubah filter, mulai penelusuran baru dari halaman pertama.

<Tip>
  Perlakukan `page_token` sebagai string opak: teruskan kembali persis seperti yang diterima.
  Jangan mendekode, menyimpan jangka panjang, atau membuat token sendiri — format internalnya
  dapat berubah kapan saja tanpa pemberitahuan.
</Tip>

<Note>
  Endpoint daftar menolak parameter kueri yang tidak didukungnya dengan `400`
  `invalid_request` yang menyebutkan nama parameter tersebut. Periksa referensi setiap endpoint untuk
  mengetahui filter yang didukung.
</Note>

## Menangani koleksi yang terputus atau berubah

Contoh-contoh ini tidak mencoba ulang error secara otomatis. Untuk `429`, patuhi `Retry-After`
dan ikuti [panduan percobaan ulang](/id/developers/v1/rate-limits) yang terbatas. Pertahankan
region, service account, versi, filter, dan token halaman terakhir yang berhasil diproses
yang sama saat mencoba ulang pembacaan. Mengganti kredensial tidak memberikan akses:
setiap permintaan tetap memeriksa izin saat ini.

Pembacaan multi-halaman bukanlah snapshot dari koleksi. Baris dapat berubah selama
Anda menelusurinya. Proses resource berdasarkan `id` yang stabil dan buat penulisan
downstream bersifat idempoten; proses mulai ulang dapat mengembalikan suatu baris lagi. Jika token ditolak,
periksa error `page_token` dan perbaiki permintaan sebelum memulai ulang dari
halaman pertama. Jangan membuang data yang telah diproses tanpa pemberitahuan atau menandai ekspor yang
terputus sebagai selesai. Untuk total pelaporan yang tepat, gunakan resource
pelaporan yang sesuai, alih-alih memperlakukan penelusuran daftar langsung sebagai snapshot.


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