Documentation

Paging

Every list, on every surface, comes back in the same envelope:

{ "items": [...], "total": 1204, "next_cursor": "50" }
FieldMeans
itemsThis page of rows.
totalHow many match in all, when that is cheap to know. Null means we do not know, not zero.
next_cursorPass back as cursor for the next page. Null means you have everything.

The rule

Follow next_cursor until it comes back null. That is the only stop condition.

In particular, a page shorter than your limit does not mean you are done. That heuristic is the most common way a client silently loses rows, so nothing here relies on it.

cursor=""
while :; do
  page=$(curl -sG -H "Authorization: Bearer $SPUTNIK_TOKEN" \
    --data-urlencode "q=your company" \
    --data-urlencode "limit=100" \
    --data-urlencode "cursor=$cursor" \
    "https://sputnikintelligence.com/api/v1/posts/search")

  echo "$page" | jq -c '.items[]'

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

The cursor is opaque

Do not build one, parse one, or assume it is a number. It is a token we issued, and what is inside it differs between endpoints. Sending one we did not issue is a 422 with code: invalid_cursor.

limit

1 to 100. Defaults to 50, or 20 for people and publications. Asking for more than 100 is a 422 rather than a silent clamp — if we quietly gave you 100 when you asked for 500, you would have no way to tell.

Two endpoints are capped, not paged

GET /topics and GET /sponsors return at most 100 rows, next_cursor is always null, and passing cursor is a 422 saying so. Narrow with q instead.

The reason is that the unfiltered ranking is "most used in the last 90 days", which stays fast however large the corpus grows, while paging deep into a million topics would not — and most of those topics appear on a single post, so nobody is reading page forty. When the cap is hit, total comes back null to tell you there were more.

Alert hits are a stream, not a page

GET /alert-hits uses the same envelope for something different. Its next_cursor is a resume token: store it, pass it back next time, and you get only what has arrived since.

It keeps coming back non-null even when there is nothing new — that is what makes it resumable. Poll it on a schedule rather than looping until null, which for this endpoint would never happen.