---
id: docs.pagination
title: "Pagination"
canonical: https://docs.secureprocessing.app/pagination
last_verified: 2026-11-06
---

# Pagination

Cursors, not page numbers.

```ts
let page = await sp.paymentIntents.list({ limit: 100 });
const all = [...page.data];

while (page.has_more) {
  page = await sp.paymentIntents.list({
    limit: 100,
    starting_after: page.data[page.data.length - 1].id,
  });
  all.push(...page.data);
}
```

## Going back

`starting_after` takes the id of the *last* object on the page you have and returns the next, older page. `ending_before` takes the id of the *first* object on the page you have and returns the page immediately before it — the `limit` objects nearest the cursor on the newer side, still newest first. From the fifth page it returns the fourth.

```ts
const back = await sp.paymentIntents.list({
  limit: 100,
  ending_before: page.data[0].id,
});
back.has_more; // true while there are newer objects still
```

Going back, `has_more` means there is more in the direction you are travelling — newer objects — and it is `false` once you have reached the newest. Pass one cursor or the other, not both. This is the same contract as Stripe’s, deliberately.

## Why not page numbers

Payments arrive constantly, and every new one shifts an offset-based page by one. Page two would skip a row or repeat one, quietly, and the row most likely to be affected is the newest — which is the one you were looking for. A cursor is stable regardless of what arrives while you are paging.

## Filtering

Lists are newest first. Payments can be filtered by status, by when they were created, by customer, and by your own metadata:

```ts
await sp.paymentIntents.list({
  status: "succeeded",
  metadata: { shop_id: "scuba-sensation" },
  created_gte: Math.floor(Date.now() / 1000) - 86_400,
});
```

Over HTTP the metadata filter is `metadata[shop_id]=scuba-sensation`, once per key; a payment matches when it has every pair, compared exactly. Only the payments list filters on metadata today — the other lists return metadata but cannot be searched by it.

`limit` is between 1 and 100, and 10 if you leave it out. Asking for more is an error rather than a silent clamp, so you never quietly get fewer rows than you planned for.
