Use cursor-based pagination to walk large lists without offsets or page numbers.
All /filter endpoints in the MVMNT API use cursor-based pagination to return large result sets in predictable, repeatable pages. The behavior is the same across resource types (orders, vendors, carriers, customers, etc.).
Filter endpoints use POST requests with pagination parameters in the request body:
POST /v1/vendors/filter
Content-Type: application/json
{
"filter": { ... },
"pageSize": 50,
"cursor": null
}All paginated responses return a consistent structure:
{
"data": [
{ "id": "...", "name": "..." },
{ "id": "...", "name": "..." }
],
"pageInfo": {
"pageSize": 50,
"hasNextPage": true,
"hasPreviousPage": false,
"endCursor": "eyJpZCI6IjU1MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMCJ9"
}
}datacontains the records for the current page.pageInfo.endCursoris the token to request the next page.- Keep paging until
pageInfo.hasNextPageisfalse.
Type: Integer Default: 50 Range: 1 - 250 Description: Target number of results to return per page (the last page may contain fewer)
{
"pageSize": 100
}Type: String (nullable) Default: null Description: Opaque cursor token returned as pageInfo.endCursor from the previous response
{
"cursor": "eyJpZCI6IjU1MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMCJ9"
}First page: Omit cursor or set to null Subsequent pages: Use endCursor from previous response
The pageInfo object in every response provides pagination metadata:
| Field | Type | Description |
|---|---|---|
pageSize | integer | Number of items returned in the current page |
hasNextPage | boolean | true if another page is available for this same query |
hasPreviousPage | boolean | true if this page is not the first page of the sequence |
endCursor | string | Cursor token for the next page (null when none remains) |
curl -X POST https://api.mvmnt.io/v1/vendors/filter \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"filter": {
"status": { "equalTo": "ACTIVE" }
},
"pageSize": 50
}'Response:
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"friendlyId": "V100001",
"name": "ABC Warehouse",
"status": "ACTIVE"
},
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"friendlyId": "V100002",
"name": "XYZ Storage",
"status": "ACTIVE"
}
// ... 48 more results
],
"pageInfo": {
"pageSize": 50,
"hasNextPage": true,
"hasPreviousPage": false,
"endCursor": "eyJpZCI6IjY2MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMSJ9"
}
}Use endCursor from the previous response:
curl -X POST https://api.mvmnt.io/v1/vendors/filter \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"filter": {
"status": { "equalTo": "ACTIVE" }
},
"pageSize": 50,
"cursor": "eyJpZCI6IjY2MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMSJ9"
}'When hasNextPage is false, you've reached the end:
{
"data": [
// ... remaining results
],
"pageInfo": {
"pageSize": 50,
"hasNextPage": false,
"hasPreviousPage": true,
"endCursor": null
}
}- Use
pageSize: 250for exports/backfills: Minimizes round trips when you need to walk the full dataset - Treat cursors as short-lived: Use them to complete the current pagination loop, not as a saved bookmark
- Drive the loop with
hasNextPage: Stop when it’sfalse; don’t infer “done” fromdata.length - Keep filters identical across pages: The cursor only makes sense for the same query inputs
- Retry on transient failures: Assume requests can fail mid-loop and retry safely
- Don't use offset-based pagination: MVMNT uses cursor-based pagination, not
pagenumbers - Don't change filters mid-pagination: Mixing a cursor with different filters produces undefined results
- Don't persist cursors: Cursors can expire or become invalid over time
- Don't assume
pageSizeequals result count: The last page can be smaller than requested - Don't paginate when you don’t need to: If your use case can tolerate a single page, request a larger
pageSize
Problem: data array is empty but no error
Possible Causes:
- Filter criteria matches no records
- All matching records are deleted (see Soft Deletes)
Solutions:
- Verify filter criteria
- Check if records exist using less restrictive filters
- Include deleted records if needed (see Filtering)
Problem: API returns error: "Invalid cursor"
Possible Causes:
- Cursor from a different query (different filter/sort)
- Cursor expired (very old cursor)
- Malformed cursor string
Solutions:
- Always use cursor from the same filter query
- Don't persist cursors - start fresh queries with
cursor: null - Ensure cursor is passed as-is without modification
Problem: Same record appears in multiple pages
Possible Causes:
- Records were created/modified during pagination
- Using cursors from different queries
Solutions:
- Accept eventual consistency for real-time data
- Deduplicate results by ID on client side
- For consistent snapshots, consider using timestamps in filters
Problem: Receiving different number of results than requested
Possible Causes:
- Last page has fewer results
pageSizeexceeds maximum (250)- Some records filtered out after query
Solutions:
- Check
pageInfo.hasNextPageinstead of counting results - Ensure
pageSizeis between 1-250 - This behavior is normal and expected
- Filtering - Learn how to filter results before pagination
- Soft Deletes - Understand how deleted records affect pagination
- API Reference - See pagination on specific endpoints