Pagination Guide - Cursor-Based Batching

This following information only applies to the /events, /teams, and /players endpoints. Other endpoints will always return all of the results which match your query. However, there may be hundreds or even thousands of results to these endpoints, so they must be fetched in batches.

How It Works

  1. Make a request. Each request has a limit on the number of items returned.
  2. If there are more items to show, you'll get a nextCursor in the response.
  3. Repeat the query but put the value from last request's nextCursor in the cursor parameter.
  4. Repeat this until you no longer receive a nextCursor in the response in order to get all of the results.

The cursor Parameter

This tells the API where to pick up from. If you received a response from the API and there are more items to show, you'll get a nextCursor at the top level of the response. Use this value in the cursor parameter of your next request to pick up where you left off.

Notes on using cursor:

  • The parameter name is exactly cursor (lowercase). The response field is called nextCursor, but sending it back as nextCursor=... returns a 400 error.
  • Always use the value from the last response's nextCursor property, unchanged. Don't try to reverse-engineer or modify the cursor value yourself.
  • Don't change any query parameters between cursor requests. This will cause the cursor to not function properly.
  • In some cases, the API may return a nextCursor when in fact there are no more items to show. So if you include a cursor parameter and get back an empty data array, that just means you've reached the end of the results.
  • Events are sorted by start time (status.startsAt) and the cursor marks your position in that ordering. Since event data updates constantly, an event can change while you're partway through paginating.
    • If an event's start time is moved to a spot you've already paged past, you won't see it in that pass. If it's moved later, you may see it twice.
    • If you filter on a field that changes over time (ex: oddsAvailable, live, started), an event may enter or leave your results between requests.

The limit Parameter

The limit parameter determines the max number of items to return in each request. It has a default value which can be overridden.

  • If you don't specify a limit parameter, then a limit of 10 will be used.
  • If you specify a limit above 300, then you'll receive an error.
  • Otherwise, the limit applied is the smaller value between the limit parameter you supplied and the max-limit for the endpoint.
  • If you're making a request to the /players or /teams endpoints, the max-limit is 250
  • If you're making a request to the /events endpoint, the max-limit varies from 25-100 depending on the query. Factors affecting this include:
    • If the query filters for specific fields (ex: oddIDs)
    • If the query is for upcoming or past events
    • If the query includes alt lines

Example

Let's take the following example, where we want to grab all unfinalized NBA events:

const allEvents = [];
let nextCursor = null;
let hasMore = true;
while (hasMore) {
  try {
    const response = await axios.get("https://api.sportsgameodds.com/v2/events", {
      params: {
        leagueID: "NBA",
        finalized: false,
        limit: 100,
        cursor: nextCursor,
      },
      headers: {
        "x-api-key": YOUR_API_KEY,
      },
    });
    allEvents.push(...response.data.data);
    nextCursor = response.data.nextCursor;
    hasMore = Boolean(nextCursor);
  } catch (error) {
    hasMore = false;
  }
}

console.log(`Found ${allEvents.length} events`);
allEvents.forEach((event) => console.log(event.eventID));

return allEvents;
import requests

all_events = []
next_cursor = None
has_more = True

while has_more:
    try:
        response = requests.get(
            "https://api.sportsgameodds.com/v2/events",
            params={
                "leagueID": "NBA",
                "finalized": "false",
                "limit": 100,
                "cursor": next_cursor
            },
            headers={"x-api-key": YOUR_API_KEY}
        )
        response.raise_for_status()
        data = response.json()

        all_events.extend(data.get("data", []))
        next_cursor = data.get("nextCursor")
        has_more = next_cursor is not None
    except requests.RequestException:
        has_more = False

print(f"Found {len(all_events)} events")
for event in all_events:
    print(event["eventID"])

return all_events
require "net/http"
require "json"
require "uri"

def fetch_all_events(api_key)
all_events = []
next_cursor = nil
has_more = true

while has_more
begin
uri = URI("https://api.sportsgameodds.com/v2/events")
params = {
leagueID: "NBA",
finalized: false,
limit: 100,
cursor: next_cursor
}.compact
uri.query = URI.encode_www_form(params)

      req = Net::HTTP::Get.new(uri)
      req["x-api-key"] = api_key

      res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
        http.request(req)
      end

      data = JSON.parse(res.body)
      all_events.concat(data["data"] || [])
      next_cursor = data["nextCursor"]
      has_more = !next_cursor.nil? && !next_cursor.empty?
    rescue StandardError
      has_more = false
    end

end

puts "Found #{all_events.length} events"
all_events.each { |event| puts event["eventID"] }

all_events
end
$allEvents = [];
$nextCursor = null;
$hasMore = true;

while ($hasMore) {
    try {
        $params = [
            'leagueID' => 'NBA',
            'finalized' => 'false',
            'limit' => 100,
        ];
        if ($nextCursor !== null) {
            $params['cursor'] = $nextCursor;
        }

        $ch = curl_init("https://api.sportsgameodds.com/v2/events?" . http_build_query($params));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            "x-api-key: " . YOUR_API_KEY
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode !== 200) {
            throw new Exception("HTTP Error: " . $httpCode);
        }

        $data = json_decode($response, true);
        $events = $data['data'] ?? [];
        $allEvents = array_merge($allEvents, $events);
        $nextCursor = $data['nextCursor'] ?? null;
        $hasMore = $nextCursor !== null;
    } catch (Exception $e) {
        $hasMore = false;
    }
}

echo "Found " . count($allEvents) . " events\n";
foreach ($allEvents as $event) {
    echo $event['eventID'] . "\n";
}

return $allEvents;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.List;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

List<JsonObject> allEvents = new ArrayList<>();
String nextCursor = null;
boolean hasMore = true;
HttpClient client = HttpClient.newHttpClient();

while (hasMore) {
try {
String url = String.format(
"https://api.sportsgameodds.com/v2/events?leagueID=NBA&finalized=false&limit=100%s",
nextCursor != null ? "&cursor=" + nextCursor : ""
);

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .header("x-api-key", YOUR_API_KEY)
            .GET()
            .build();

        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        JsonObject data = JsonParser.parseString(response.body()).getAsJsonObject();

        data.getAsJsonArray("data").forEach(event ->
            allEvents.add(event.getAsJsonObject())
        );

        nextCursor = data.has("nextCursor") && !data.get("nextCursor").isJsonNull()
            ? data.get("nextCursor").getAsString()
            : null;
        hasMore = nextCursor != null;
    } catch (Exception e) {
        hasMore = false;
    }

}

System.out.println("Found " + allEvents.size() + " events");
allEvents.forEach(event ->
System.out.println(event.get("eventID").getAsString())
);

return allEvents;

On this page