Pagination
How list endpoints page their results, and which parameters each one takes.
Every endpoint that returns a collection returns one page of it, described by the same pagination object. The table below gives the page parameters, the size limits and the list key for each endpoint.
One pair works everywhere
Every paginated endpoint accepts current_ and per_ — the same spelling the pagination object uses in the response, so you can page with the value you just read:
cURL
curl --request GET --url 'https://api.themooncard.com/v2/api-gateway/cards?current_page=2&per_page=25' --header 'accept: application/json' --header 'x-api-key: YOUR_API_KEY'Each endpoint also keeps the parameter names it has always accepted, listed below. They mean exactly the same thing, carry the same limits, and keep working. If you send both spellings in one request, current_ and per_ win.
Which parameters each list takes
Swap the host for https://api.themooncard.com in production.
| Endpoint | Page parameters | Size limits | List key in the response |
|---|---|---|---|
V1 GET /card | page, limit, current_, per_ | page default 1; limit 1–49, default 10 | cards |
V2 GET /cards | page, limit, current_, per_ | page default 1; limit 1–49, default 10 | cards |
V1 GET /controls/velocity | page, current_, per_ | page default 1; per_ applied as at most 100, default 10 | velocityControls |
V2 GET /controls/velocity | page, current_, per_ | page default 1; per_ applied as at most 100, default 10 | velocityControls |
V2 GET /accounts | page, current_, per_ | page minimum 1; per_ 1–100 | accounts |
V2 GET /accounts/{id}/cards | page, current_, per_ | page minimum 1; per_ 1–100 | cards |
V2 GET /cardholders | page, current_, per_ | page default 1; per_ 1–100, default 10 | cardholders |
V2 GET /cardholders/{id}/cards | page, current_, per_ | page default 1; per_ 1–100, default 10 | cards |
V2 GET /organizations | page, current_, per_ | page default 1; per_ below 50, default 10 | organizations |
V2 GET /organizations/{id}/children | page, current_, per_ | page default 1; per_ below 50, default 10 | organizations |
V1 GET /card/card-products | current_, per_ | current_ default 1; per_ minimum 1, default 10 | card_ |
V1 GET /card/{card_ | current_, per_ | current_ default 1; per_ 1–50, default 10 | transactions |
V2 GET /cards/{card_ | current_, per_ | current_ default 1; per_ 1–50, default 10 | transactions |
V2 GET /accounts/{id}/transactions | current_, per_ | current_ minimum 1; per_ 1–50 | transactions |
Elsewhere the cap is validated and a larger value is rejected with a 400: GET /accounts at 100, the organization listings at 49, the card listings at 49, and the transaction feeds at 50.
Three details worth reading off the table before you write a loop:
- The transaction feeds also answer to
currentPageandperPage, the spelling they carried before the canonical pair existed.GET /accounts/{id}/transactionstakes its date filters asstart_anddate end_, and still acceptsdate startDateandendDate. - The two card-product listings use
current_andpage per_. Those are the parameters they read; send the page you want with those two keys.page velocityControlsis the list key on both velocity-control listings. The rest of the envelope issnake_.case
Lists that take no page parameters
The velocity status endpoints return the full set as a bare JSON array, with no wrapper key and no pagination object:
- V1 and V2
GET /controls/velocity/card/{card_id}/status - V1 and V2
GET /controls/velocity/cardholder/{external_id}/status - V2
GET /controls/velocity/status
GET /controls/velocity/{id}/status is not a list at all: it returns a single status object for one control.
The pagination object
Every paginated endpoint returns the same object next to its list. All eight fields are always present.
| Field | Type | Meaning |
|---|---|---|
total | integer | Total number of items across all pages. |
per_ | integer | Page size that was applied. |
current_ | integer | The page you are on, 1-based. |
last_ | integer | Number of the last page. With no items at all it is 0 on some listings and 1 on others, so branch on total or on next_, not on last_. |
prev_ | integer or null | Previous page number; null on the first page. |
next_ | integer or null | Next page number; null on the last page. |
from | integer | 1-based index of the first item on this page; 0 when the page is empty. |
to | integer | 1-based index of the last item on this page; 0 when the page is empty. |
The pagination object is snake_ on every endpoint.
cURL
curl --request GET \
--url 'https://api.themooncard.com/v1/api-gateway/card/card-products?current_page=1&per_page=10' \
--header 'accept: application/json' \
--header 'x-api-key: YOUR_API_KEY'Walking every page
Follow pagination.next_ until it is null. That works on every paginated endpoint and does not require you to compute anything.
Node.js
async function listAllCards(apiKey) {
const cards = [];
let page = 1;
for (;;) {
const response = await fetch(
`https://api.themooncard.com/v1/api-gateway/card?page=${page}&limit=49`,
{ headers: { "x-api-key": apiKey, accept: "application/json" } },
);
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const body = await response.json();
cards.push(...body.cards);
if (body.pagination.next_page === null) return cards;
page = body.pagination.next_page;
}
}Two things to keep in mind while you loop:
- Lists are ordered newest first, and the underlying data can change between requests. An item created while you are paging can shift later items onto the next page. If exactness matters, reconcile by
idrather than assuming each page is disjoint. - Space the requests out. A long paging loop can reach the request limit — see Rate limits for the ceiling, the
X-RateLimit-*headers and theRetry-Afteron a429.
A page past the end
Requesting a page beyond last_ returns 200 with an empty list. total and last_ still describe the real data and next_ is null. from and to describe the window that was asked for rather than the rows returned, so on an overflow page from can exceed to — read the list itself, and stop when next_ is null.
JSON
{
"cards": [],
"pagination": {
"total": 12,
"last_page": 2,
"prev_page": 1,
"next_page": null,
"per_page": 10,
"current_page": 9,
"from": 81,
"to": 12
}
}Filtering a list by an id
Filter a list by an identifier that does not exist — or that belongs to another organization — and you get 200 with an empty list and total: 0.
| Request | Unknown or unowned id gives |
|---|---|
GET /card?external_ (V1), GET /cards?external_ (V2) | 200, empty cards |
GET /card?card_ (V1), GET /cards?card_ (V2) | 200, empty cards |
GET /card/{card_ (V1), GET /cards/{card_ (V2, with an API key) | 200, empty transactions |
total: 0 therefore means either that the id is real and has nothing attached to it, or that the id is not one of yours. To confirm an id exists, fetch the resource directly — GET /card/{card_, GET /cardholders/{id} — which returns 404 for an id outside your organization.
Where the id is a path segment, the parent resource is resolved first:
GET /v2/api-gateway/accounts/{id}/cards—404when the account is unknown or not yours;200with an empty list andtotal: 0when the account is real and funds no card.GET /v2/api-gateway/cardholders/{id}/cards—404when the cardholder is unknown or not yours. When it is yours,pagination.totalcounts every card ever linked to it, including deleted ones the page filters out, sototalcan exceed the number of items you can page through. Count the items you receive when you need the number of live cards.GET /controls/velocity?card_—id=… 404withcode: "CARD_when the card is unknown or not yours.NOT_ FOUND" GET /v2/api-gateway/accounts/{id}/transactions—404when the account is unknown or not yours. While the activity feed is momentarily unavailable it returns200with an empty list; retry to confirm a feed you expect to hold entries.
An id that is not a well-formed UUID fails validation before the lookup and comes back as a 400. See Errors for that body and for the difference between validation 400s and { message, code } errors.
Related
- Fetching card transactions — the transaction feed, its entry types and
include_.loads - Data formats and ids — which id belongs on which endpoint, and how amounts and timestamps are typed.
- API versions — how V1 and V2 relate.
Last updated August 17, 2026