Skip to content
Loading keys…

Pagination

Offset pages with per_page and page, or cursors for large exports.

Every list endpoint returns an items array and a meta object. Two modes share the same endpoint: offset pagination by default, cursor pagination as soon as you send a cursor. A numbered page also carries the next_cursor that continues it, so a cursor walk starts with a plain request. meta tells you which mode answered: current_page in offset mode, prev_cursor in cursor mode.

GEThttps://api.wajub.com/payments

Parameters

per_pageintegeroptionaldefault : 25
Items per page, from 1 to 100. Anything outside that range is a 422. Tax lists default to 50.
pageintegeroptionaldefault : 1
Page number, for offset pagination: a whole number from 1. Anything else is a 422.
cursorstringoptional
An opaque cursor: the meta.next_cursor of a previous response, a numbered page included. Sending it switches the endpoint to cursor mode and page is ignored.

Filters are per resource, not global. On GET /payments:

statusstringoptional
One of pending, processing, succeeded, expired, partial, failed, cancelled, refunded, partially_refunded.
date_fromdateoptional
Lower bound on created_at, as YYYY-MM-DD.
date_todateoptional
Upper bound on created_at. Must not precede date_from.
amount_minnumberoptional
Minimum amount, in the major unit.
amount_maxnumberoptional
Maximum amount. Must not be below amount_min.
searchstringoptional
Free text over reference, description and customer details.

Offset pagination

The default. Ask for a page number, get a meta that tells you how many there are in total, and where to go on with a cursor:

GEThttps://api.wajub.com/payments?per_page=2&status=succeeded
curl "https://api.wajub.com/payments?per_page=2&status=succeeded" \
-H "Authorization: $WAJUB_API_KEY"
Response · 200 OK
{
"code": 200,
"status": "OK",
"message": "Payments retrieved",
"items": [
{
"id": "trx_CSUGajfv9xh0XQ5wu2lx",
"reference": "order-1042",
"status": "succeeded",
"amount": 5000,
"currency": "XAF"
},
{
"id": "trx_9Wm4Kd2pR7vB5nL6hY1c",
"reference": "order-1043",
"status": "succeeded",
"amount": 12000,
"currency": "XAF"
}
],
"meta": {
"current_page": 1,
"last_page": 69,
"per_page": 2,
"total": 137,
"next_cursor": "c_9fK2mQ7vB4nL6hR1xT8w",
"has_more": true
}
}

has_more says whether a page follows this one, and next_cursor is the cursor of the last item of the page: null on the last page. Both are there so that you can continue with a cursor. To stay in offset mode, ignore them.

Loop until current_page reaches last_page. Use the largest page size you can, 100, so a 137-item history is two requests rather than six:

async function allPayments(filters = {}) {
const all = [];
let page = 1;

while (true) {
  const params = new URLSearchParams({ ...filters, per_page: '100', page: String(page) });
  const res = await fetch(`https://api.wajub.com/payments?${params}`, {
    headers: { Authorization: process.env.WAJUB_API_KEY },
  });
  const { items, meta } = await res.json();

  all.push(...items);

  if (page >= meta.last_page) break;
  page += 1;
}

return all;
}

Cursor pagination

Offset pages shift when new items arrive while you walk them, and get slower on long histories. Cursors avoid both: pass cursor and each page is anchored on the last item you saw.

There is no cursor to send on the first request, so start without one. The first page is a numbered page, and its meta carries next_cursor. Send it back as cursor and the walk goes on in cursor mode, right after the last item you received, with no item skipped or served twice. It works from any numbered page, not only the first.

meta changes shape in cursor mode. current_page, last_page and total are gone, replaced by:

Cursor response
{
"code": 200,
"status": "OK",
"message": "Payments retrieved",
"items": [
],
"meta": {
"per_page": 25,
"next_cursor": "c_4Tz8Wm2Qx6Hb9Lc3NdP5k",
"prev_cursor": null,
"has_more": true
}
}

Feed next_cursor back as cursor and stop when has_more is false:

async function* streamPayments() {
// No cursor on the first request: that page already carries next_cursor.
let cursor = null;

do {
  const params = new URLSearchParams({ per_page: '100' });

  if (cursor) params.set('cursor', cursor);

  const res = await fetch(`https://api.wajub.com/payments?${params}`, {
    headers: { Authorization: process.env.WAJUB_API_KEY },
  });
  const { items, meta } = await res.json();

  yield* items;
  cursor = meta.has_more ? meta.next_cursor : null;
} while (cursor);
}

Which endpoints support what

Cursor mode is available wherever the list has a stable sort on created_at. Three lists are offset only: their meta has no next_cursor and no has_more.

EndpointOffsetCursorList key
GET /paymentsYesYesitems
GET /transfersYesYesitems
GET /refundsYesYesitems
GET /customersYesYesitems
GET /beneficiariesYesYesitems
GET /invoicesYesYesitems
GET /linksYesYesitems
GET /accountsYesYesitems
GET /disputesYesYesitems
GET /webhooksYesYesitems
GET /eventsYesNoevents
GET /tax/codes, /tax/jurisdictionsYesNosee Tax

GET /invoices and GET /links accept sort_by and sort_dir. Cursor mode needs their default order, newest first: with another sort, cursor is ignored and the numbered page carries neither next_cursor nor has_more. Page by number there.

Prefer webhooks over polling

To react to a status change, subscribe to webhooks instead of paginating in a loop. Pagination is for exports, reconciliation and history screens.

What did you think of this content?