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
- Make a request. Each request has a limit on the number of items returned.
- If there are more items, you get a
nextCursorin the response. - Repeat the query with that value in the
cursorparameter. - Repeat until
nextCursoris 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./teamsand/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.