Pagination Guide — Cursor-Based Batching

This applies to /events/, /teams/, /players/ and /markets/. Other endpoints always return every result that matches your query.

How it works

  1. Make a request. Each request has a limit on the number of items returned.
  2. If there are more items, you get a nextCursor in the response.
  3. Repeat the query with that value in the cursor parameter.
  4. Repeat until nextCursor is null.

The cursor parameter

  • Always use the value from the last response's nextCursor. Don't reverse-engineer it.
  • Don't change any other query parameters between cursor requests.
  • Percent-encode the cursor — some ids contain non-ASCII characters.

The limit parameter

  • /events: default 10, max 100.
  • /teams and /players: default 50, max 250.
  • /markets: default 100, max 10,000.

Example

Grab every unfinalized NRL event:

const allEvents = []; let cursor = null;
do {
  const url = new URL("https://api.sockodds.com/v2/events");
  url.search = new URLSearchParams({ leagueID: "NRL", finalized: "false", limit: "100", ...(cursor ? { cursor } : {}) });
  const r = await fetch(url, { headers: { "x-api-key": KEY } });
  const body = await r.json();
  allEvents.push(...body.data); cursor = body.nextCursor;
} while (cursor);
console.log(`Found ${allEvents.length} events`);
import requests
all_events, cursor = [], None
while True:
    r = requests.get("https://api.sockodds.com/v2/events", params={"leagueID": "NRL", "finalized": "false", "limit": 100, **({"cursor": cursor} if cursor else {})}, headers={"x-api-key": KEY})
    r.raise_for_status(); body = r.json()
    all_events.extend(body["data"]); cursor = body.get("nextCursor")
    if not cursor: break
print(f"Found {len(all_events)} events")
require "net/http"; require "json"
all = []; cursor = nil
loop do
  uri = URI("https://api.sockodds.com/v2/events"); uri.query = URI.encode_www_form({ leagueID: "NRL", finalized: false, limit: 100, cursor: cursor }.compact)
  req = Net::HTTP::Get.new(uri); req["x-api-key"] = KEY
  body = JSON.parse(Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }.body)
  all.concat(body["data"]); cursor = body["nextCursor"]; break unless cursor
end
$all = []; $cursor = null;
do {
  $params = ['leagueID' => 'NRL', 'finalized' => 'false', 'limit' => 100]; if ($cursor) $params['cursor'] = $cursor;
  $ch = curl_init("https://api.sockodds.com/v2/events?" . http_build_query($params));
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-api-key: $KEY"]);
  $body = json_decode(curl_exec($ch), true); curl_close($ch);
  $all = array_merge($all, $body['data']); $cursor = $body['nextCursor'] ?? null;
} while ($cursor);
List<JsonObject> all = new ArrayList<>(); String cursor = null;
do {
  String url = "https://api.sockodds.com/v2/events?leagueID=NRL&finalized=false&limit=100" + (cursor != null ? "&cursor=" + URLEncoder.encode(cursor, "UTF-8") : "");
  HttpRequest req = HttpRequest.newBuilder().uri(URI.create(url)).header("x-api-key", KEY).GET().build();
  JsonObject body = JsonParser.parseString(client.send(req, HttpResponse.BodyHandlers.ofString()).body()).getAsJsonObject();
  body.getAsJsonArray("data").forEach(e -> all.add(e.getAsJsonObject()));
  cursor = body.has("nextCursor") && !body.get("nextCursor").isJsonNull() ? body.get("nextCursor").getAsString() : null;
} while (cursor != null);
SockOdds noteWithout a date window, eventID, finalized or includeFinished=true, events that kicked off more than 24 hours ago are omitted from listings — the source never prunes finished events, so this keeps the default page current.
Need help?FAQ · Email · Contact