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

# 페이지네이션

> 목록 envelope, limit 매개변수, 불투명 page_token 커서를 사용하여 Living Security Public API 목록을 순회하는 방법을 설명합니다.

모든 `/v1` 목록 엔드포인트는 동일한 envelope를 반환하며
동일한 방식으로 페이지네이션합니다. 토큰 기반이며 순방향으로만 진행됩니다.

## 목록 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">
  항상 `"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 사이의 정수입니다. 이 범위를 벗어난 값은 코드 `invalid_request`이(가) 포함된
  `400`을(를) 반환합니다.
</ParamField>

<ParamField query="page_token" type="string">
  이전 응답의 `next_page_token`입니다. 첫 번째 페이지에서는 생략하십시오.
</ParamField>

오프셋 매개변수, 이전 페이지로의 탐색, 정확한 총 개수는 제공되지 않습니다. 컬렉션의 개수를 세려면 컬렉션을 끝까지 순회해야 합니다.

<Note>
  **예외:** `GET /v1/cohorts/{id}/members`은(는) 더 높은 `limit`을(를) 요청하더라도 페이지당 최대 50개의 행만 반환합니다. `has_more` 및
  `next_page_token`은(는) 여전히 코호트의 실제 나머지 항목을 반영합니다.
</Note>

두 가지 Basic 자격 증명 형태 중 하나로 인증하십시오. 자세한 내용은
[빠른 시작](/ko/developers/v1/quickstart)(`apikey:secret` 또는 `secret:`)을 참조하십시오.
`BASE`을(를) 조직의 리전 호스트로 설정하십시오. 자세한 내용은
[리전](/ko/developers/v1/regions)을 참조하십시오.

## 목록 순회

빠른 시작에서와 같이 시크릿 관리자에서 `LIVING_SECURITY_API_KEY`을(를) 불러오십시오.
이 예제에는 `entity:read`이(가) 필요합니다. 첫 번째 요청 가이드에서 사용한 키에는
이 추가 권한이 필요할 수 있습니다. Python 예제에는 `requests`을(를), 셸
예제에는 `jq`을(를) 설치하십시오. 두 예제 모두 버전을 고정하고, 필터를 변경하지 않으며, 리디렉션을 거부합니다. 오류가 발생하거나 1,000페이지에 도달하면 중단하며, 일부만 순회한 결과를 완료된 것으로 보고하지 않습니다. Python의 읽기 타임아웃은 전체 작업 시간이 아니라 비활성 시간을 제한하므로, 통합 환경에서 작업 데드라인을 설정하십시오.

`next_page_token`이(가) `null`이(가) 될 때까지 페이지를 요청하십시오. 대량 읽기에는 `limit=500`을(를) 사용하면 더 적은 요청으로 컬렉션 전체를 가져올 수 있습니다. 자세한 내용은
[요청 제한](/ko/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` 필터를 추가하는 경우) `page_token` 매개변수에 대해 코드 `invalid_request`이(가) 포함된 `400`이(가) 반환됩니다.

```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`을(를) 준수하고
제한된 [재시도 가이드](/ko/developers/v1/rate-limits)를 따르십시오. 읽기를 재시도할 때는
동일한 리전, 서비스 계정, 버전, 필터 및 마지막으로 성공적으로 처리한 페이지 토큰을 유지하십시오. 자격 증명을 변경해도 접근 권한이 부여되지 않습니다.
각 요청은 항상 현재 권한을 확인합니다.

여러 페이지에 걸친 읽기는 컬렉션의 스냅샷이 아닙니다. 순회하는 동안 행이 변경될 수 있습니다. 안정적인 `id`을(를) 기준으로 리소스를 처리하고 하위 쓰기 작업이 멱등성을 갖도록 만드십시오. 다시 시작하면 동일한 행이 다시 반환될 수 있습니다. 토큰이 거부되면 `page_token` 오류를 확인하고 요청을 수정한 후 첫 페이지부터 다시 시작하십시오. 이전에 처리한 데이터를 조용히 폐기하거나 중단된 내보내기를 완료된 것으로 표시하지 마십시오. 정확한 보고용 총계가 필요하면 실시간 목록 순회를 스냅샷으로 취급하지 말고 해당 보고 리소스를 사용하십시오.


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