> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.awardspring.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.awardspring.com/_mcp/server.

# Pagination

List endpoints never return everything. Each returns one page wrapped in the same envelope, plus opaque cursors for moving through the rest.

```json
{
  "object": "list",
  "url": "/api/v1/gifts",
  "has_more": true,
  "next_cursor": "AauGZNw_di2MaHLBgeWvyBwU5nXCIKvIBN99nWFDrlfQy0mRyBTj3zOCETV3AKmdePZkZRlz7IwekKAOriAM-Vt8D5FqMqaN3u79VNmfG8_fL-NTygeyP-M",
  "previous_cursor": null,
  "data": [ ]
}
```

| Field             | Meaning                                                                                                           |
| ----------------- | ----------------------------------------------------------------------------------------------------------------- |
| `object`          | Always `"list"`.                                                                                                  |
| `url`             | The path this collection was served from.                                                                         |
| `has_more`        | Whether another page exists in the direction you are paging.                                                      |
| `next_cursor`     | Pass as `starting_after` to move forward. `null` on the last page.                                                |
| `previous_cursor` | Pass as `ending_before` to move backward.                                                                         |
| `total_count`     | How many records match the request across all pages. Only present when you ask for it with `include=total_count`. |
| `data`            | The resources for this page.                                                                                      |

## Parameters

| Parameter        | Description                                                                  |
| ---------------- | ---------------------------------------------------------------------------- |
| `limit`          | Page size. Defaults to `25`.                                                 |
| `starting_after` | Page forward from this cursor.                                               |
| `ending_before`  | Page backward from this cursor.                                              |
| `include`        | Optional extras, comma-separated. `total_count` is the only supported value. |

`limit` is **clamped, not rejected**. Values outside `1`–`100` are silently coerced into range, so `limit=5000` returns 100 rather than an error. Do not rely on the number you sent being the number you get.

`starting_after` and `ending_before` are mutually exclusive. If you send both, `starting_after` wins.

## Paging forward

Read `next_cursor` from the response and send it back as `starting_after`. Stop when `has_more` is `false`.

```bash
# first page
curl -G https://api.awardspring.com/api/v1/gifts \
  -H "X-Spring-API-Key: YOUR_KEY" \
  --data-urlencode "limit=25"

# next page
curl -G https://api.awardspring.com/api/v1/gifts \
  -H "X-Spring-API-Key: YOUR_KEY" \
  --data-urlencode "limit=25" \
  --data-urlencode "starting_after=AauGZNw_di2MaHLBgeWvyBwU5nXCIKvIBN99nWFDrlfQy0mRyBTj3zOCETV3AKmdePZkZRlz7IwekKAOriAM-Vt8D5FqMqaN3u79VNmfG8_fL-NTygeyP-M"
```

## Asking for the total

Lists do not count their records unless you ask. Add `include=total_count` and the response carries `total_count`, the number of records that match your request across every page, not just this one. Leave it out and the field is not in the response at all.

```bash
curl -G https://api.awardspring.com/api/v1/gifts   -H "X-Spring-API-Key: YOUR_KEY"   --data-urlencode "type=pledge"   --data-urlencode "include=total_count"
```

```json
{
  "object": "list",
  "url": "/api/v1/gifts",
  "has_more": true,
  "next_cursor": "AauGZNw_di2MaHLBgeWvyBwU5nXCIKvIBN99nWFDrlfQy0mRyBTj3zOCETV3AKmdePZkZRlz7IwekKAOriAM-Vt8D5FqMqaN3u79VNmfG8_fL-NTygeyP-M",
  "previous_cursor": null,
  "total_count": 1342,
  "data": [ ]
}
```

**The count is taken at the moment of the request.** It is worked out fresh each time you ask, so if records are added or removed while you page, a later count can differ from the first. Ask for it on the first page, where it describes the list you are about to walk, rather than on every page.

`include` is not a filter. Asking for the total on one page and not the next never makes a cursor invalid. Any value other than `total_count` returns `400 validation_failed`.

## Send the same filters on every page

This is the part that surprises people. **A cursor remembers the filters that produced it.** Send `q`, `donor_id`, `type` and similar on the first request, then send them again, with the same values, alongside the cursor on every later page. A later page whose filters differ from the cursor's, including one that leaves a filter out, returns `400 invalid_cursor` rather than quietly paging through a different list.

Filters that take an ID, such as `donor_id` and `award_cycle_id`, take the record's [9-character ID](/ids) exactly as the API returned it. On the scholarship reporting endpoints `award_cycle_id` is required on every request, first page or not. A cursor taken while a filter held an older whole-number ID returns `400 invalid_cursor`; start again from the first page.

```bash
# first page: the filter
curl -G https://api.awardspring.com/api/v1/scholarships/awarded-students \
  -H "X-Spring-API-Key: YOUR_KEY" \
  --data-urlencode "award_cycle_id=C7RJ3M9QV"

# later pages: the same filter, plus the cursor
curl -G https://api.awardspring.com/api/v1/scholarships/awarded-students \
  -H "X-Spring-API-Key: YOUR_KEY" \
  --data-urlencode "award_cycle_id=C7RJ3M9QV" \
  --data-urlencode "starting_after=AQZ_QUkpFKz7keOS3XOv9NgpNh6sOwfTcenOSpBvYE7MQvGB7hPgWSz30FBcMcW8keay5KUyj9pb3u9j-w7AfjPP1xhCVEM98OP-lJ_Cie_KtYhy1O0gK6tvB3vmGAzbVHRlcH3JGNKRl24"
```

## Cursors are opaque and encrypted

A cursor is an encrypted token bound to your institution and to the filters that produced it. It is not a page number, an offset, or a record id, and it cannot be read: decoding one shows nothing about your data.

Asking for the same page twice returns a different cursor each time. Both work; never compare cursors to decide whether you have seen a page before.

Never build, edit, truncate, or store one for later reuse. A cursor that has been tampered with, has expired, belongs to another institution, or disagrees with the filters on the request returns `400 invalid_cursor`.

If you need to resume work later, re-run the first request rather than persisting a cursor.