# Pagination

Ask for a page size, then follow links.next until it is null.

## How a list is paged

Every list route returns one page at a time. You choose the page size with `limit`, and the response tells you where the next page is.

| Route | Default `limit` | Largest `limit` |
| --- | --- | --- |
| `/v1/leagues`, `/v1/bookmakers`, `/v1/markets`, `/v1/teams`, `/v1/players`, `/v1/events`, `/v1/odds`, `/v1/results` | 25 | 100 |
| `/v1/odds/history` | 250 | 1,000 |

On `/v1/odds` the limit counts events, and each event carries all of its prices. `/v1/sports` is short enough to arrive on one page. A `limit` outside the allowed range is refused with 400.

## What a page tells you

| Field | Meaning |
| --- | --- |
| `meta.count` | How many items this page holds. It can be less than `limit`, on the last page or when a filter leaves fewer. |
| `meta.cursor` | An opaque cursor for the next page, or `null` when this is the last page. |
| `links.next` | The full URL of the next page: your request with the cursor added. `null` on the last page. |

Here is the first page of the NFL's markets, two at a time:

```bash
curl -sS "https://api.sockodds.com/v1/markets?league=nfl&limit=2" \
  -H "Authorization: Bearer $SOCKODDS_API_KEY"
```

```javascript
const response = await fetch("https://api.sockodds.com/v1/markets?league=nfl&limit=2", {
  headers: { Authorization: `Bearer ${process.env.SOCKODDS_API_KEY}` },
});
console.log(JSON.stringify(await response.json(), null, 2));
```

```python
import json, os, urllib.request

request = urllib.request.Request(
    "https://api.sockodds.com/v1/markets?league=nfl&limit=2",
    headers={"Authorization": "Bearer " + os.environ["SOCKODDS_API_KEY"]},
)
with urllib.request.urlopen(request) as response:
    print(json.dumps(json.load(response), indent=2))
```

Response (from a local copy of the API with invented test prices):

```json
{
  "data": [
    {
      "key": "total_points_odd_even",
      "name": "Total points odd/even",
      "subject": "event",
      "bet": "odd_even",
      "stat": "points",
      "has_line": false,
      "sides": [
        "odd",
        "even"
      ],
      "sports": [
        "american_football"
      ],
      "periods": [
        "full",
        "half_1",
        "half_2",
        "quarter_1",
        "quarter_2",
        "quarter_3",
        "quarter_4"
      ]
    },
    {
      "key": "team_points_odd_even",
      "name": "Team points odd/even",
      "subject": "team",
      "bet": "odd_even",
      "stat": "points",
      "has_line": false,
      "sides": [
        "odd",
        "even"
      ],
      "sports": [
        "american_football"
      ],
      "periods": [
        "full"
      ]
    }
  ],
  "meta": {
    "count": 2,
    "cursor": "eyJuIjoyLCJmIjoiOWY5N2IyMTM5MzZhNjRmOSJ9"
  },
  "links": {
    "next": "https://api.sockodds.com/v1/markets?league=nfl&limit=2&cursor=eyJuIjoyLCJmIjoiOWY5N2IyMTM5MzZhNjRmOSJ9"
  }
}
```

## Follow links.next unchanged

To read the next page, request `links.next` exactly as it came back. Keep going until `links.next` is `null`.

- Do not build the next URL yourself, and do not decode or edit the cursor. Its format is not part of the contract and can change.
- A cursor belongs to the query that produced it. Change a filter or the limit and the cursor no longer fits: the API answers 400 with `Invalid cursor`. Start again from the first page instead.
- Send your key only to `https://api.sockodds.com`. The programs below keep the path and query of `links.next` exactly as given and always send them to the API's own address, so the key can never be sent anywhere else.

## Read every page

This program reads every NFL market, ten at a time, and says how many pages and markets it read.

```javascript
const API = "https://api.sockodds.com";
const headers = { Authorization: `Bearer ${process.env.SOCKODDS_API_KEY}` };

let url = `${API}/v1/markets?league=nfl&limit=10`;
let pages = 0;
let markets = 0;
while (url) {
  const response = await fetch(url, { headers });
  const body = await response.json();
  if (!response.ok) throw new Error(`${body.status} ${body.title}: ${body.detail}`);
  pages += 1;
  markets += body.meta.count;
  console.log(`Page ${pages}: ${body.meta.count} markets`);
  // links.next is null on the last page. Keep its path and query exactly as given,
  // and send them only to the SockOdds API.
  const next = body.links.next ? new URL(body.links.next) : null;
  url = next ? API + next.pathname + next.search : null;
}
console.log(`Read ${markets} markets in ${pages} ${pages === 1 ? "page" : "pages"}.`);
```

```python
import json, os, urllib.request
from urllib.parse import urlsplit

API = "https://api.sockodds.com"
headers = {"Authorization": "Bearer " + os.environ["SOCKODDS_API_KEY"]}

url = API + "/v1/markets?league=nfl&limit=10"
pages = 0
markets = 0
while url:
    with urllib.request.urlopen(urllib.request.Request(url, headers=headers)) as response:
        body = json.load(response)
    pages += 1
    markets += body["meta"]["count"]
    print(f"Page {pages}: {body['meta']['count']} markets")
    # links.next is null on the last page. Keep its path and query exactly as given,
    # and send them only to the SockOdds API.
    next_link = body["links"]["next"]
    if next_link:
        parts = urlsplit(next_link)
        url = API + parts.path + ("?" + parts.query if parts.query else "")
    else:
        url = None
print(f"Read {markets} markets in {pages} {'page' if pages == 1 else 'pages'}.")
```

Output (from a local copy of the API with invented test prices):

```text
Page 1: 10 markets
Page 2: 9 markets
Read 19 markets in 2 pages.
```

## Tips

- Use the largest `limit` you can handle when you want everything: fewer pages means fewer requests against your per-minute limit. See [Limits](https://sockodds.com/docs/limits/).
- Lists of events change as games are added, start and finish. Each page is read when you ask for it, so if the list changes during a long walk an event can appear on two pages or move past you. Key what you store by event `id`, and poll again rather than treating one walk as a frozen copy.
- History pages work the same way, with their own limits. See [History and snapshots](https://sockodds.com/docs/history/).
