Skip to content

Commit c941316

Browse files
Mlaz-codeclaude
andcommitted
docs: document cursor-based pagination as recommended approach for /odds
Add ?cursor= param to odds.mdx and overview.mdx. Explain why offset-based pagination drifts on live data, add a dedicated Pagination section with full cURL/JS/Python examples using next_cursor, and update response JSON samples to show next_cursor alongside next_offset. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b7842c7 commit c941316

2 files changed

Lines changed: 80 additions & 37 deletions

File tree

content/en/api-reference/odds.mdx

Lines changed: 72 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ The sportsbooks returned in your results depend on your subscription tier. Free
3737
| `group_by` | string || Group results by field (e.g., `event`) |
3838
| `state` | string || US state code for sportsbook deep links (e.g., `nj`, `ny`, `il`). When set, `deep_link` URLs include `?state=XX` so the redirect targets the correct state-specific sportsbook domain. Only affects books with state-dependent URLs (BetMGM, Caesars, BetRivers). |
3939
| `limit` | integer | 50 | Max results per page (max 200) |
40-
| `offset` | integer | 0 | Pagination offset (max 5000) |
40+
| `offset` | integer | 0 | Pagination offset. May produce duplicate rows when live data updates between requests — use `cursor` for multi-page scans. |
41+
| `cursor` | string || Opaque cursor from `next_cursor` in a previous response. **Recommended for multi-page scans** — stable against live data changes. Takes precedence over `offset` when both are provided. |
4142

4243
<Callout type="info">
4344
Use comma-separated values to filter by multiple sportsbooks: `sportsbook=draftkings,fanduel,betmgm`
@@ -83,46 +84,83 @@ curl -X GET "https://api.sharpapi.io/api/v1/odds?league=nba&sportsbook=draftking
8384
</Tabs.Tab>
8485
<Tabs.Tab>
8586
```javascript
86-
const response = await fetch(
87-
'https://api.sharpapi.io/api/v1/odds?league=nba&sportsbook=draftkings&market=moneyline',
88-
{ headers: { 'X-API-Key': 'YOUR_API_KEY' } }
89-
);
90-
const { data, meta } = await response.json();
91-
console.log(`Received ${meta.count} of ${meta.total} odds`);
92-
93-
// Paginate through results
94-
if (meta.pagination.has_more) {
95-
const nextPage = await fetch(
96-
`https://api.sharpapi.io/api/v1/odds?league=nba&sportsbook=draftkings&offset=${meta.pagination.next_offset}`,
97-
{ headers: { 'X-API-Key': 'YOUR_API_KEY' } }
98-
);
87+
// Fetch all NBA moneyline odds using cursor-based pagination (recommended)
88+
async function fetchAllOdds() {
89+
const results = [];
90+
let cursor = null;
91+
92+
do {
93+
const url = new URL('https://api.sharpapi.io/api/v1/odds');
94+
url.searchParams.set('league', 'nba');
95+
url.searchParams.set('sportsbook', 'draftkings');
96+
url.searchParams.set('market', 'moneyline');
97+
url.searchParams.set('limit', '200');
98+
if (cursor) url.searchParams.set('cursor', cursor);
99+
100+
const { data, pagination } = await fetch(url, {
101+
headers: { 'X-API-Key': 'YOUR_API_KEY' }
102+
}).then(r => r.json());
103+
104+
results.push(...data);
105+
cursor = pagination.has_more ? pagination.next_cursor : null;
106+
} while (cursor);
107+
108+
return results;
99109
}
100110
```
101111
</Tabs.Tab>
102112
<Tabs.Tab>
103113
```python
104114
import requests
105115

106-
response = requests.get(
107-
'https://api.sharpapi.io/api/v1/odds',
108-
params={
109-
'league': 'nba',
110-
'sportsbook': 'draftkings',
111-
'market': 'moneyline'
112-
},
113-
headers={'X-API-Key': 'YOUR_API_KEY'}
114-
)
115-
result = response.json()
116-
print(f"Found {result['meta']['count']} of {result['meta']['total']} odds")
117-
118-
# Check for more pages
119-
if result['meta']['pagination']['has_more']:
120-
next_offset = result['meta']['pagination']['next_offset']
121-
print(f"Next page at offset {next_offset}")
116+
# Fetch all NBA moneyline odds using cursor-based pagination (recommended)
117+
def fetch_all_odds():
118+
results = []
119+
params = {'league': 'nba', 'sportsbook': 'draftkings', 'market': 'moneyline', 'limit': 200}
120+
cursor = None
121+
122+
while True:
123+
if cursor:
124+
params['cursor'] = cursor
125+
resp = requests.get(
126+
'https://api.sharpapi.io/api/v1/odds',
127+
params=params,
128+
headers={'X-API-Key': 'YOUR_API_KEY'}
129+
).json()
130+
131+
results.extend(resp['data'])
132+
pagination = resp['pagination']
133+
if not pagination['has_more']:
134+
break
135+
cursor = pagination['next_cursor']
136+
137+
return results
122138
```
123139
</Tabs.Tab>
124140
</Tabs>
125141

142+
## Pagination
143+
144+
<Callout type="warning">
145+
**Use cursor-based pagination for multi-page scans.** The `/odds` endpoint serves live data that refreshes every ~15 seconds. With offset-based pagination, rows can shift positions between requests, causing duplicates at page boundaries. Cursor-based pagination anchors each page to the last seen item — no drift.
146+
</Callout>
147+
148+
Each response includes both `next_cursor` (stable) and `next_offset` (legacy) in the `pagination` object. For sequential full-dataset scans, always use `next_cursor`.
149+
150+
```bash
151+
# First page — no cursor needed
152+
curl "https://api.sharpapi.io/api/v1/odds?league=nfl&limit=200" \
153+
-H "X-API-Key: YOUR_API_KEY"
154+
155+
# Subsequent pages — pass next_cursor from the previous response
156+
curl "https://api.sharpapi.io/api/v1/odds?league=nfl&limit=200&cursor=eyJlIjoiMzM0ODMxNTMiLCJiIjoiZHJhZnRraW5ncyIsIm0iOiJtb25leWxpbmUiLCJpIjoiZHJhZnRraW5nc18zMzQ4MzE1M19tb25leWxpbmVfUEhJIn0" \
157+
-H "X-API-Key: YOUR_API_KEY"
158+
```
159+
160+
Cursors are opaque — do not parse or construct them. They encode the sort position of the last item on the current page and are only valid for the same filter parameters.
161+
162+
`?offset=N` continues to work and is appropriate for single-page requests or direct position access. It is not recommended for iterating through live data.
163+
126164
## Response
127165

128166
### Success (200)
@@ -179,7 +217,8 @@ if result['meta']['pagination']['has_more']:
179217
"limit": 50,
180218
"offset": 0,
181219
"has_more": true,
182-
"next_offset": 50
220+
"next_offset": 50,
221+
"next_cursor": "eyJlIjoiMzM0ODMxNTMiLCJiIjoiZHJhZnRraW5ncyIsIm0iOiJtb25leWxpbmUiLCJpIjoiZHJhZnRraW5nc18zMzQ4MzE1M19tb25leWxpbmVfUEhJIn0"
183222
},
184223
"updated_at": "2026-01-26T02:10:37.846Z",
185224
"filters": {
@@ -403,8 +442,8 @@ curl "https://api.sharpapi.io/api/v1/odds?league=nba&live=true" \
403442
curl "https://api.sharpapi.io/api/v1/odds?event=33483153&market=moneyline,spread" \
404443
-H "X-API-Key: YOUR_API_KEY"
405444

406-
# Paginate through all NFL spread odds
407-
curl "https://api.sharpapi.io/api/v1/odds?league=nfl&market=spread&limit=100&offset=100" \
445+
# Paginate through all NFL spread odds (use next_cursor from each response)
446+
curl "https://api.sharpapi.io/api/v1/odds?league=nfl&market=spread&limit=200" \
408447
-H "X-API-Key: YOUR_API_KEY"
409448
```
410449

content/en/api-reference/overview.mdx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ All successful responses wrap data in a consistent envelope:
140140
"limit": 50,
141141
"offset": 0,
142142
"has_more": true,
143-
"next_offset": 50
143+
"next_offset": 50,
144+
"next_cursor": "eyJlIjoiMzM0ODMxNTMiLCJiIjoiZHJhZnRraW5ncyIsIm0iOiJtb25leWxpbmUiLCJpIjoiZHJhZnRraW5nc18zMzQ4MzE1M19tb25leWxpbmVfUEhJIn0"
144145
},
145146
"meta": {
146147
"count": 1,
@@ -149,7 +150,8 @@ All successful responses wrap data in a consistent envelope:
149150
"limit": 50,
150151
"offset": 0,
151152
"has_more": true,
152-
"next_offset": 50
153+
"next_offset": 50,
154+
"next_cursor": "eyJlIjoiMzM0ODMxNTMiLCJiIjoiZHJhZnRraW5ncyIsIm0iOiJtb25leWxpbmUiLCJpIjoiZHJhZnRraW5nc18zMzQ4MzE1M19tb25leWxpbmVfUEhJIn0"
153155
},
154156
"updated_at": "2026-01-26T02:10:37.846Z",
155157
"filters": {
@@ -167,7 +169,8 @@ All successful responses wrap data in a consistent envelope:
167169
| `pagination.limit` | Maximum items per page (default: 50, max: 200) |
168170
| `pagination.offset` | Current offset into results |
169171
| `pagination.has_more` | Whether more results exist beyond this page |
170-
| `pagination.next_offset` | Offset value to use for the next page |
172+
| `pagination.next_offset` | Offset value to use for the next page (legacy — use `next_cursor` for live-data endpoints) |
173+
| `pagination.next_cursor` | Opaque cursor for the next page. Pass as `?cursor=` — stable against live data changes. Recommended for multi-page scans. |
171174
| `meta.count` | Number of items returned in this response |
172175
| `meta.total` | Total matching items across all pages |
173176
| `meta.pagination` | Same as top-level `pagination` (included for backward compatibility) |
@@ -265,7 +268,8 @@ All list endpoints support filtering via query parameters. Filters use **singula
265268
| `max_odds` | number | `200` | Maximum American odds filter |
266269
| `group_by` | string | `event` | Group results by field |
267270
| `limit` | number | `25` | Results per page (default: 50, max: 200) |
268-
| `offset` | number | `50` | Pagination offset |
271+
| `offset` | number | `50` | Pagination offset. May produce duplicate rows on live endpoints when data changes between requests. |
272+
| `cursor` | string || Opaque cursor from `next_cursor`. Recommended for multi-page scans over live data — eliminates offset drift. |
269273

270274
### Examples
271275

0 commit comments

Comments
 (0)