Skip to content

List endpoints in the Titan Public API support two pagination modes:

  • Cursor (keyset) pagination — opaque cursor pointing at the next page. Stable across concurrent updates.
  • Offset pagination — classic limit + offset. Simple but can skip or duplicate rows when the underlying set changes between requests.

Cursor is the recommended mode for anything scanning more than a few pages or resuming a paused sync. Offset is fine for small, one-off queries.

Request parameters

Every paginated endpoint accepts:

ParameterTypeDefaultMeaning
limitintegerserver defaultMaximum rows returned per page.
offsetinteger0Zero-based offset. Ignored when cursor is set.
cursorstring(unset)Opaque cursor. "" = request the first cursor-based page. Omit entirely to use offset mode.

Which mode you're in

  • cursor unset → offset mode. limit + offset drive paging.
  • cursor present (including empty string "") → cursor mode. offset is ignored; limit still caps page size.

Response envelope

{
  "observations": [ ],
  "next_cursor": "eyJvZmZzZXQiOjEwMCwic25hcHNob3QiOiIyMDI2LTA3LTAxIn0="
}
  • The primary result field contains the current page.
  • next_cursor — pass as cursor on the next call. When absent or empty, you have reached the end.

The cursor is opaque — treat it as a bag of bytes. Do not decode, mutate, or persist parts of it.

CURSOR=""
while :; do
  RESP=$(curl -sX POST "https://titanapi.securityscorecard.io/public/v1/observations" \
    -H "Authorization: Bearer $TITAN_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d "{\"limit\": 100, \"cursor\": \"$CURSOR\"}")

  echo "$RESP" | jq '.observations[]'

  CURSOR=$(echo "$RESP" | jq -r '.next_cursor // ""')
  [ -z "$CURSOR" ] && break
done

Iterating with offset pagination

OFFSET=0
LIMIT=100
while :; do
  RESP=$(curl -sX POST "https://titanapi.securityscorecard.io/public/v1/observations" \
    -H "Authorization: Bearer $TITAN_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d "{\"limit\": $LIMIT, \"offset\": $OFFSET}")

  COUNT=$(echo "$RESP" | jq '.observations | length')
  [ "$COUNT" -eq 0 ] && break

  echo "$RESP" | jq '.observations[]'
  OFFSET=$((OFFSET + LIMIT))
done

Guidance

  • Prefer cursor for anything non-trivial. Cursor is stable across updates; offset can silently skip or duplicate rows when observations arrive mid-scan.
  • Do not decode the cursor. It's opaque — do not persist parts of it, mutate it, or compare cursors for ordering.
  • Do not persist cursors across long gaps. Cursors typically expire after 24 hours. Restart from cursor="" and dedupe by resource id.
  • Do not parallelise a single stream. Give each worker its own disjoint filter (e.g. by time range) instead of splitting one cursor across workers.
  • Stop on empty next_cursor (cursor mode) or empty results (offset mode). A short page in cursor mode is not necessarily the last page — always check next_cursor.