Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ This project is ideal for:
## Documentation

- [Getting Started Guide](docs/getting-started.md) - Set up the project and make your first API calls
- [Pagination Guide](docs/pagination.md) - Learn how cursor-based pagination works across the API

---

Expand Down
2 changes: 2 additions & 0 deletions docs/api-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ This document explains the standard design guidelines and API response conventio
5. [Amount Format](#5-amount-format)
6. [Error Shape](#6-error-shape)

For a practical walkthrough of cursor-based pagination across StellarKit endpoints, see [Pagination Guide](pagination.md).

---

## 1. Response Envelope
Expand Down
162 changes: 162 additions & 0 deletions docs/pagination.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Pagination Guide

Many list endpoints in StellarKit support cursor-based pagination so clients can walk through large result sets without requesting everything at once.

## What cursor-based pagination means

Cursor-based pagination works by sending a small page of results and then using a token from that page to request the next page.

- `limit` controls how many records are returned in each page.
- `cursor` is an opaque token returned by a previous response. It tells StellarKit where to continue reading.
- The cursor value usually represents the paging position of the last record on the current page, so the next request resumes from that point.
- You should treat the cursor as an opaque string. Do not try to parse it or infer page numbers from it.

## Request parameters

The pagination parameters used by StellarKit are:

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `limit` | number | varies by endpoint | Maximum number of records to return in one response. |
| `cursor` | string | none | Token from a previous response that resumes pagination from the next item. |
| `order` | string | `desc` | Optional sort direction (`asc` or `desc`) on endpoints that support it. |

## How to paginate

1. Send the first request without a `cursor`.
2. Read the response. The response payload includes the next page token in the pagination fields, usually as `data.cursor`.
3. If the cursor is `null`, you have reached the last page and there is nothing else to fetch.
4. If the cursor contains a value, repeat the request with the same endpoint and the new `cursor` value.

## How to detect the last page

A paginated response is complete when one of these is true:

- `data.cursor` is `null`
- the returned items are fewer than the requested `limit`
- the endpoint explicitly reports that no more results are available

In practice, the simplest rule is: if the response has no cursor, stop paging.

## Endpoints that use this pattern

The same cursor-based pattern is used by endpoints such as:

- `GET /account/:id/payments`
- `GET /account/:id/offers`
- `GET /account/:id/offer-history`
- `GET /transactions/:id`
- `GET /transactions/:id/operations`
- `GET /account/:id/transactions/search`
- `GET /asset/:code/:issuer/holders`

## Step-by-step example: paginate through all payments for an account

The following example walks through every payment for a Stellar account using `/account/:id/payments`.

### Step 1: Request the first page

```http
GET /account/GA.../payments?limit=2
```

Example response:

```json
{
"success": true,
"data": {
"items": [
{
"type": "payment",
"amount": "10.0000000",
"sender": "GABC...",
"receiver": "GXYZ..."
},
{
"type": "payment",
"amount": "25.0000000",
"sender": "G123...",
"receiver": "GXYZ..."
}
],
"total": 2,
"limit": 2,
"cursor": "page-2-token"
}
}
```

The response tells you that there are more results because `data.cursor` contains a value.

### Step 2: Request the next page

Use the returned cursor in the next request:

```http
GET /account/GA.../payments?limit=2&cursor=page-2-token
```

Example response:

```json
{
"success": true,
"data": {
"items": [
{
"type": "payment",
"amount": "5.0000000",
"sender": "G999...",
"receiver": "GXYZ..."
}
],
"total": 1,
"limit": 2,
"cursor": null
}
}
```

Because `data.cursor` is `null`, this is the last page.

### Step 3: Stop paging

When the cursor is `null`, stop making requests. You have fetched all available pages for that account and the collection is complete.

## Example loop in JavaScript

```js
async function fetchAllPayments(accountId) {
const allPayments = [];
let cursor = null;

while (true) {
const url = new URL(`/account/${accountId}/payments`, "http://localhost:3000");
url.searchParams.set("limit", "2");
if (cursor) {
url.searchParams.set("cursor", cursor);
}

const response = await fetch(url);
const payload = await response.json();

allPayments.push(...payload.data.items);

if (!payload.data.cursor) {
break;
}

cursor = payload.data.cursor;
}

return allPayments;
}
```

## Best practices

- Keep the same `limit` value across pages for consistent page sizes.
- Preserve the cursor exactly as returned by the API.
- Stop paging as soon as the cursor becomes `null`.
- If you need a specific ordering, include `order=asc` or `order=desc` consistently on each request.
Loading