Cursors

/findings, /assets and /scan-activity return pages:
{
  "items": [ … ],
  "page": { "size": 200, "next_cursor": "eyJ2IjoxLCJwIjoiY29kZV9z…", "has_more": true },
  "meta": { … }
}
To read the next page, send the same request with cursor set to page.next_cursor. Stop when has_more is false and next_cursor is null. A page can hold fewer than limit items, or none, while has_more is still true; this happens on incremental pulls whose filters match little of what changed. Keep following the cursor.
  • limit sets the page size: default 200, maximum 1000. Larger values are capped at 1000.
  • Keep every other parameter the same while paging. A cursor used with different parameters is refused with 400 invalid_cursor.
  • If the key’s scope is edited, or the organization’s plan changes, while you page, the next page returns 409 cursor_stale. Start again without a cursor.
  • Cursors are opaque. Don’t build or modify them.
/frameworks and /controls are not paged. Each returns its full result in one response.

Two ways to read findings

GET /findings works in one of two modes, chosen by whether you send updated_since.

Snapshot

Without updated_since, the response is the product’s current state: every open and suppressed finding in the key’s scope. Pass status=open,suppressed,resolved to include resolved findings as well. Findings are ordered by a stable key, so paging never skips or repeats a finding because another one changed. For Code Security, the whole pagination reads repositories as they were scanned at meta.snapshot_at: a rescan that finishes while you page appears in the next sync, not halfway through this one. Use a snapshot for the first sync, and for platforms that replace their whole copy on each sync.

Incremental

With updated_since, the response lists every finding whose state changed after that time: new findings, severity or triage changes, and resolutions. Resolved findings are included by default, so you see findings close. On the last page, meta.next_updated_since gives the value to send next time:
# First incremental pull
curl -H "Authorization: Bearer $KEY" \
  "$BASE/findings?product=cloud_security&updated_since=2026-09-29T00:00:00Z"

# On the last page: "meta": { "next_updated_since": "2026-09-30T08:49:55.120Z", … }
# Next pull
curl -H "Authorization: Bearer $KEY" \
  "$BASE/findings?product=cloud_security&updated_since=2026-09-30T08:49:55.120Z"
next_updated_since is 10 minutes before the moment the pull covered up to (meta.snapshot_at). The overlap catches changes that were still being written when you read. Some findings appear in two consecutive pulls as a result, so upsert findings by id; never append. updated_since must be within the product’s history window, meta.tombstone_horizon_days (also in whoami). Older values return 400 updated_since_out_of_range, because a finding resolved and deleted before then can’t be reported. Run a snapshot instead.
1

Daily: full snapshot

Read every page of a snapshot for each product. Mark findings you hold that are missing from it as no longer present.
2

Hourly: incremental pull

Pull with updated_since set to the last next_updated_since, and upsert each finding by id.
The daily snapshot is needed because some findings leave the key’s view without a change of their own, so no incremental pull reports them. For example:
  • the key’s scope is edited to exclude a repository or finding type;
  • a repository is deactivated, or its default branch changes;
  • a device is removed, or re-enrolled so that it appears as a new device;
  • your plan stops including a finding type.

Ledger products

Cloud Security, Kubernetes Security, Container Security and Pentest store each scan’s results without a history of their own. For these products TigerGate keeps a history by comparing each completed scan with the one before:
  • A finding in the new scan that wasn’t in the previous one opens. Its first_detected_at is the time of that scan.
  • A finding missing from the new scan resolves. status_detail gives the reason: not_detected, passing (the cloud check now passes), or resource_removed (the cloud resource, cluster, account or target is gone).
  • A cloud finding closes only when the new scan ran its check in its region. A scan narrowed to other services, checks, severities, frameworks or regions leaves it open.
  • Failed and partial scans never resolve findings.
  • If one scan would resolve more than 500 findings of a single account, cluster, image or target, and more than half of those open, the resolutions are held until a second, newer scan agrees.
Scans are compared within a few minutes of completing. whoami shows these products with sync_semantics: ledger. When a key first gains one of them, TigerGate builds the history from stored scan results. Until that finishes, requests for the product return 503 ledger_initializing with a Retry-After header, and whoami shows ledger_state: initializing. Findings present before the first build carry first_detected_precision: backfill: their first_detected_at is the earliest time TigerGate can show, not necessarily when the issue first appeared. The other products (sync_semantics: lifecycle) keep a full history of their own and are read directly.