Pagination and filtering
How to paginate each API list, which filters each resource accepts, and how to fetch many resources in one call.
Pagination is resource-specific. There is no single contract that covers every list. Before you wire up a list, read the meta block that endpoint returns. That block is authoritative.
This page describes the shipment model, which is the most complete, and then the simple model the other resources use.
Paginate shipments by page (the default)
GET /v1/shipments uses page-number pagination.
GET /v1/shipments?page=1&limit=50
| Parameter | Default | Maximum | Description |
|---|---|---|---|
page |
1 | — | Page number |
limit |
20 | 100 | Items per page |
{
"success": true,
"data": ["..."],
"meta": {
"pagination": {
"mode": "offset",
"page": 1,
"limit": 50,
"total": 1234,
"totalPages": 25,
"hasNextPage": true,
"hasPrevPage": false
}
}
}
Keep going while hasNextPage is true.
Paginate by cursor when volume grows
On large lists, counting every page becomes expensive. Turn on cursor mode with useCursor=true.
GET /v1/shipments?useCursor=true&limit=50
GET /v1/shipments?useCursor=true&limit=50&cursor=eyJjcmVhdGVkQXQiOi...
useCursor accepts only true or false. Omit it, or send false, to paginate by offset.
{
"success": true,
"data": ["..."],
"meta": {
"pagination": {
"mode": "cursor",
"limit": 50,
"hasNextPage": true,
"nextCursor": "eyJjcmVhdGVkQXQiOi..."
}
}
}
The nextCursor value is opaque. Pass it back unchanged. Do not decode it or build one yourself. Stop when hasNextPage is false.
let cursor;
do {
const url = new URL("https://api.sendit.mx/v1/shipments");
url.searchParams.set("useCursor", "true");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const { data, meta } = await fetch(url, {
headers: { "X-API-Key": process.env.SENDIT_API_KEY },
}).then((r) => r.json());
process(data);
cursor = meta.pagination.hasNextPage ? meta.pagination.nextCursor : null;
} while (cursor);
Paginate the other resources
Every other paginated list uses a flat meta block. There is no pagination level and no cursor:
GET /v1/wallet/transactions?page=2&limit=50
{
"success": true,
"data": ["..."],
"meta": { "page": 2, "limit": 50, "total": 340, "totalPages": 7 }
}
The wallet (monedero), orders, products, and invoices all work this way. Some resources do not paginate at all. In every case, the meta block in the response is authoritative.
Filter the results
Filters are named query parameters. There are no field[gte] operators, no sort=, no fields=, and no expand[]. The exact set depends on the resource.
For GET /v1/shipments:
| Parameter | Description |
|---|---|
status |
A single status |
statuses |
Several statuses, comma-separated. Takes precedence over status when you send both |
carrierCode |
Carrier |
trackingNumber |
Partial match on the tracking number |
externalId |
Exact match |
search |
Case-insensitive free text across tracking number, externalId, and destination (contact name, city, state) |
createdFrom |
Created on or after this date (inclusive) |
createdTo |
Created before this date (exclusive) |
GET /v1/shipments?status=DELIVERED&carrierCode=DHL
GET /v1/shipments?statuses=DELIVERED,RETURNED
GET /v1/shipments?search=FEDMX123
GET /v1/shipments?createdFrom=2026-01-01&createdTo=2026-04-01
The date range is half-open. It includes createdFrom and excludes createdTo. You can chain months without fetching a record twice.
Sorting
The order is fixed: createdAt descending, with id as the tie-breaker. Shipment lists accept no custom sort parameter.
Fetch many known resources at once
If you already have the IDs, do not paginate. The bulk endpoints return up to 100 resources in one call:
POST /v1/bulk/shipments/fetch
{ "ids": ["shp_aaa", "shp_bbb", "shp_ccc"] }
Errors come back per item. One missing ID does not fail the whole batch. All four bulk endpoints (shipments, orders, tracking, address validation) are documented alongside batches.