> ## Documentation Index
> Fetch the complete documentation index at: https://docs.teasy.link/api-v1/llms.txt
> Use this file to discover all available pages before exploring further.

# Pagination

> Cursor-based lists, and how to walk one to the end.

Every list endpoint answers in the same envelope, and you page through it by handing the cursor
back.

```json theme={null}
{
  "object": "list",
  "data": [],
  "pagination": {
    "has_more": true,
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wOC0yMVQxMDowMDowMFoifQ"
  }
}
```

Pass `next_cursor` back as `?cursor=`. When `has_more` is `false`, `next_cursor` is `null` and
there is nothing left to fetch.

`limit` accepts 1–100 and defaults to 25. Sort order is fixed: newest first.

```bash theme={null}
curl "https://api.teasy.link/v1/redirects?limit=100&cursor=eyJjcmVhdGVkX2F0IjoiMjAyNi0wOC0yMVQxMDowMDowMFoifQ" \
  -H "Authorization: Bearer tsl_live_<32 characters>"
```

<Warning>
  **The cursor is opaque.** It is not a page number, an id or a timestamp, and its format may change
  without notice. Pass back exactly what we gave you — a cursor you built or edited yourself is not
  guaranteed to work.
</Warning>

## Walking a list

<CodeGroup>
  ```ts TypeScript theme={null}
  async function* listAll(path: string) {
    let cursor: string | null = null;

    do {
      const query = new URLSearchParams({ limit: "100" });
      if (cursor) query.set("cursor", cursor);

      const res = await fetch(`https://api.teasy.link/v1${path}?${query}`, {
        headers: { Authorization: `Bearer ${process.env.TEASY_KEY}` },
      });
      if (!res.ok) throw new Error((await res.json()).error.code);

      const page = await res.json();
      yield* page.data;
      cursor = page.pagination.next_cursor;
    } while (cursor);
  }

  for await (const redirect of listAll("/redirects")) {
    console.log(redirect.id, redirect.url);
  }
  ```

  ```python Python theme={null}
  import os, requests

  def list_all(path):
      cursor = None
      while True:
          params = {"limit": 100}
          if cursor:
              params["cursor"] = cursor

          res = requests.get(
              f"https://api.teasy.link/v1{path}",
              params=params,
              headers={"Authorization": f"Bearer {os.environ['TEASY_KEY']}"},
          )
          res.raise_for_status()

          page = res.json()
          yield from page["data"]

          cursor = page["pagination"]["next_cursor"]
          if not cursor:
              return

  for redirect in list_all("/redirects"):
      print(redirect["id"], redirect["url"])
  ```
</CodeGroup>

There is no total count. If you need one, page through and tally.

## Filtering by group

`GET /v1/redirects` and `GET /v1/landings` accept `group_id`.

| Request           | Returns             |
| ----------------- | ------------------- |
| No parameter      | All your links      |
| `?group_id=grp_…` | Links in that group |
| `?group_id=none`  | Links with no group |

An unknown group id returns an **empty list, not a `404`**, so a sync job pointed at a group that
someone has since deleted keeps running instead of erroring on every pass.

This filter is the only way to read a group's contents — groups come back flat, with counters. See
[Links and groups](/api-v1/api-v1/links#groups).
