Pagination

Every list endpoint in the Distribu API is cursor-paginated. Instead of ?page=2, you follow an opaque cursor that points at the last item you've seen. This is the standard approach for large, sorted datasets — inserts and deletes between pages don't break your iteration.

Query parameters

Both list endpoints (GET /api/v1/products and GET /api/v1/orders) accept:

ParamTypeDefaultMax
limitinteger50100
cursorstring (item ID)— (start at the beginning)

A limit smaller than 1 is bumped to 1. Larger than 100 is clamped down to 100. A non-numeric limit (like ?limit=abc) falls back to the default of 50.

Response envelope

Every list endpoint wraps its results in:

{
  "data": [ /* array of records */ ],
  "pagination": {
    "hasMore": true,
    "nextCursor": "clxxABCDE..."
  }
}
  • data — the records for this page, up to limit items.
  • pagination.hasMoretrue if more records exist after this page.
  • pagination.nextCursor — the ID to pass as ?cursor= for the next page. null on the final page.

Ordering

All list endpoints sort by createdAt descending — newest first. Within a page, items are in that order. The cursor is the ID of the last item on the page.

Cursor-based pagination doesn't support other orderings today. If you need ascending order or sorting by a different field, pull all the pages and sort client-side, or use the dashboard UI (which supports more sort options).

A typical loop

#!/bin/bash
CURSOR=""
while true; do
  # Build URL with optional cursor
  if [ -z "$CURSOR" ]; then
    URL="https://distribu.app/api/v1/orders?limit=100"
  else
    URL="https://distribu.app/api/v1/orders?limit=100&cursor=$CURSOR"
  fi

  # Fetch page
  RESPONSE=$(curl -s -H "Authorization: Bearer $API_KEY" "$URL")

  # Process records with jq or your tool of choice
  echo "$RESPONSE" | jq '.data[]'

  # Check for more
  HAS_MORE=$(echo "$RESPONSE" | jq -r '.pagination.hasMore')
  if [ "$HAS_MORE" != "true" ]; then break; fi

  CURSOR=$(echo "$RESPONSE" | jq -r '.pagination.nextCursor')
done

The same pattern in Node.js:

async function* paginate(url, apiKey) {
  let cursor = null;
  while (true) {
    const qs = new URLSearchParams({ limit: "100" });
    if (cursor) qs.set("cursor", cursor);
    const res = await fetch(`${url}?${qs}`, {
      headers: { Authorization: `Bearer ${apiKey}` },
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const { data, pagination } = await res.json();
    for (const item of data) yield item;
    if (!pagination.hasMore) return;
    cursor = pagination.nextCursor;
  }
}

for await (const order of paginate(
  "https://distribu.app/api/v1/orders",
  process.env.API_KEY
)) {
  console.log(order.id, order.status, order.total);
}

Combining with filters

Cursor pagination composes with every filter on the list endpoints. For example, paginating only SHIPPED orders:

GET /api/v1/orders?status=SHIPPED&limit=100
GET /api/v1/orders?status=SHIPPED&limit=100&cursor=clxxABC...

The cursor is opaque (don't construct it by hand) but it implicitly carries your filter context — changing the filter between calls is undefined behavior. If you want to re-filter, drop the cursor and start over.

Invalid cursors

If you pass a cursor value that doesn't correspond to a record in your company, the API returns an empty data array with hasMore: false — not a 404. Always pass cursors you received from a previous response; don't construct them by hand.

Total count

There's no totalCount field in the response. We don't do a SELECT COUNT(*) on every list request because it's expensive on large tables and would slow down what should be a fast paginated read.

If you need an exact count for a specific filter, paginate through and count client-side, or use the dashboard (which shows "N orders" in the list header and uses a separate count query).

Page-based pagination

Not supported on the API. The dashboard UI uses page-based URLs like ?page=3&perPage=20 but those are a UI affordance built on a different query layer. The API is cursor-only.


Next: Errors.