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:
| Param | Type | Default | Max |
|---|---|---|---|
limit | integer | 50 | 100 |
cursor | string (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 tolimititems.pagination.hasMore—trueif more records exist after this page.pagination.nextCursor— the ID to pass as?cursor=for the next page.nullon 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.
