본문으로 건너뛰기
Sursa Developers

페이지네이션

Sursa API의 목록은 커서로 페이지를 나눠요. 최대 limit개의 항목을 요청하면 응답이 다음 페이지를 받는 방법을 알려 줘요. 오프셋을 계산할 필요가 없고, 그 사이에 데이터가 바뀌어도 페이지 사이에서 항목이 중복되거나 빠지지 않아요.

파라미터값
limit페이지당 항목 수. 보통 1~200이에요. 새 목록의 기본값은 50이며, 목록별 범위와 기본값은 레퍼런스에 나와 있어요.
cursor이전 페이지에서 받은 커서. 첫 페이지에서는 생략하세요.

목록은 응답 형태에 따라 두 가지 방식 중 하나로 다음 커서를 반환해요. 어느 방식을 쓰는지는 API 레퍼런스의 각 엔드포인트 페이지에 나와 있어요.

data로 감싼 목록

이벤트 피드처럼 공개 API를 위해 추가된 목록은 data에 항목을 담은 객체를 반환해요.

curl "https://api.sursa.com/v1/webhook-events?limit=2" \
  -H "Authorization: Bearer $SURSA_API_KEY"
{
  "data": [
    {"id": "evt_8a1f…", "type": "card.published", "created_at": "2026-10-24T10:00:00Z"},
    {"id": "evt_8a20…", "type": "card.updated", "created_at": "2026-10-24T10:00:03Z"}
  ],
  "next_cursor": "eyJ2IjoxLCJrIjpbMTA0Ml19",
  "has_more": true
}

has_more가 true이면 cursor에 next_cursor 값을 넣고 같은 필터로 다음 페이지를 요청하세요.

curl "https://api.sursa.com/v1/webhook-events?limit=2&cursor=eyJ2IjoxLCJrIjpbMTA0Ml19" \
  -H "Authorization: Bearer $SURSA_API_KEY"

커서가 도입되기 전부터 있던 목록 중 객체를 반환하는 목록은 고유한 래퍼를 그대로 써요(예: {"items": […], "total": 120}). 이런 목록은 같은 객체 안에 next_cursor와 has_more가 추가되고, 항목은 원래 자리에 그대로 있어요.

배열을 반환하는 목록

몇몇 목록은 GET /v1/collections처럼 순수 JSON 배열을 반환해요. 응답 형태를 바꿀 수 없으므로 다음 페이지 정보는 헤더로 와요.

curl -i "https://api.sursa.com/v1/collections?limit=50" \
  -H "Authorization: Bearer $SURSA_API_KEY"
HTTP/1.1 200 OK
Link: <https://api.example.com/v1/collections?limit=50&cursor=eyJ2IjoxLCJrIjpbNTBdfQ>; rel="next"
X-Next-Cursor: eyJ2IjoxLCJrIjpbNTBdfQ

[{"id": 1, "name": "support", …}, …]
  • rel="next"가 있는 Link 헤더는 다음 페이지의 전체 주소예요. X-Next-Cursor 헤더에는 같은 커서만 따로 담겨요.
  • rel="next"가 있는 Link 헤더도, X-Next-Cursor 헤더도 없으면 마지막 페이지예요.
  • limit 없이 호출하면 이 목록은 예전처럼 모든 항목을 한 번의 응답으로 반환해요. 페이지로 나누려면 limit 값을 보내세요. 커서가 페이지 크기를 기억하므로 다음 요청에서는 limit 값을 생략해도 돼요.
  • GET /v1/cards는 예외예요. 이 목록은 항상 페이지로 나누어 반환하며, 기본값은 카드 200장이고 limit으로 최대 1000장까지 늘릴 수 있어요. 그러니 limit 없이도 X-Next-Cursor 헤더를 따라가세요.
// Every item of a list that returns an array.
async function* listArray(path) {
  let next = `https://api.sursa.com${path}?limit=200`;
  while (next) {
    const response = await fetch(next, { headers: { Authorization: `Bearer ${process.env.SURSA_API_KEY}` } });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    yield* await response.json();
    const cursor = response.headers.get("X-Next-Cursor");
    next = cursor ? `https://api.sursa.com${path}?cursor=${encodeURIComponent(cursor)}` : null;
  }
}

이벤트 피드

이벤트 피드 GET /v1/webhook-events는 로그이므로 페이지 방식이 조금 달라요.

  • 이벤트는 오래된 것부터 오름차순으로 정렬돼요.
  • next_cursor는 has_more가 false일 때도 모든 페이지에서 반환돼요. 이 값을 저장해 두었다가 나중에 같은 커서로 다시 요청하면 그 뒤에 일어난 이벤트를 받을 수 있어요. 피드는 원하는 속도로 읽을 수 있는 대기열이에요.
  • 이벤트는 해당 변경이 일어난 뒤 약 1초 안에 피드에 나타나며, 이미 읽은 이벤트보다 앞에 나타나는 일은 없어요.
  • 피드에는 조직 전체의 이벤트가 담기므로, 피드를 읽으려면 admin 역할과 webhooks:manage 스코프가 있는 키가 필요해요.

완전한 폴링 루프는 데이터 웨어하우스로 내보내기에서 볼 수 있어요.

커서 규칙

  • 커서는 최대 512자의 불투명한 문자열이에요. 형식이 바뀔 수 있으니 파싱하거나 직접 만들지 마세요.
  • 커서는 하나의 목록과 하나의 필터 조합에 속해요. 손상되었거나, 다른 목록에서 왔거나, 다른 필터와 함께 보낸 커서는 400 상태와 api.cursor_invalid 코드를 반환해요.
  • 각 목록에는 고유한 키로 끝나는 고정된 정렬 순서가 있어서, 모든 항목은 정확히 한 페이지에만 나타나요.
  • 커서와 함께 추가된 목록은 전체 개수를 반환하지 않아요. 전부터 total 값이 있던 목록은 그 값을 계속 반환해요. 전체 개수가 필요하면 읽으면서 항목을 세세요.

오프셋 페이지네이션

일부 목록은 커서가 도입되기 전에 offset을 받았고, 기존 연동이 계속 동작하도록 지금도 받아요. 새 코드에서는 cursor를 쓰세요. offset과 cursor는 함께 쓸 수 없으며, 둘 다 보내면 422 상태와 validation.failed 코드를 반환해요.