Overview
The Luminous External V1 API uses two different pagination styles depending on the endpoint you are calling:
- Offset (page-number) pagination — used by standard list endpoints such as sales orders, products, purchase orders, companies, etc.
- Cursor (keyset) pagination — used by dedicated bulk-export endpoints designed for large, consistent snapshots.
Understanding which style applies to the endpoint you are using will let you reliably walk through every record in a large data set.
Path prefix varies by endpoint. Most list endpoints (sales orders, products) live under /external/v1/. Newer endpoints, including the bulk exports, invoices and inbound shipments, live under /external/api/v1/. Check each endpoint's reference page for its exact path.
1. Offset Pagination (standard list endpoints)
Most GET list endpoints (for example, /external/v1/sales-orders, /external/v1/products, /external/v1/purchase-orders) use standard page-number pagination.
Request parameters
| Parameter | Default | Maximum | Description |
|---|---|---|---|
page | 1 | — | The page number to retrieve. Starts at 1. |
per_page | 10 | 100 | How many records to return per page. |
Example request (page 2, 50 records per page):
GET /external/v1/sales-orders?page=2&per_page=50
Response structure
Every offset-paginated response wraps the records in a data array and includes two helper objects:
{
"data": [ ...records... ],
"links": {
"first": "https://yourstore.com/external/v1/sales-orders?page=1",
"last": "https://yourstore.com/external/v1/sales-orders?page=10",
"prev": "https://yourstore.com/external/v1/sales-orders?page=1",
"next": "https://yourstore.com/external/v1/sales-orders?page=3"
},
"meta": {
"current_page": 2,
"from": 51,
"last_page": 10,
"per_page": 50,
"to": 100,
"total": 500
}
}
Key fields to use when paging:
meta.current_page— the page you just received.meta.last_page— the final page; stop whencurrent_pageequals this value.links.next— the pre-built URL for the next page (can be used directly).meta.total— total number of matching records across all pages.
Tips for offset pagination
- Combine with filters to narrow the result set before paging (for example,
?created_at[gte]=2024-01-01&per_page=100). - The default sort order is by
updated_atdescending. If records are being updated while you page, consider filtering by a stable date range so pages remain consistent. - Request up to 100 records per page to minimize the number of round trips.
2. Cursor Pagination (bulk export endpoints)
Bulk export endpoints are designed for complete, consistent snapshots of large catalogs or stock positions. They use an opaque cursor token instead of page numbers, which guarantees a stable walk even while new data is being written.
The two bulk export endpoints that use cursor pagination are:
| Endpoint | Purpose |
|---|---|
GET /external/api/v1/products/export | Full product catalog export |
GET /external/api/v1/inventory/stocks/export | Full current stock snapshot |
Products export — request parameters
| Parameter | Required | Description |
|---|---|---|
since | Yes, on the first request only | ISO date/datetime. Sets the snapshot cutoff. Do not send this on follow-up pages. |
cursor | Yes, on every request after the first | Opaque token from the previous response's next_cursor. |
limit | No | Records per page. Default: 100. Maximum: 1000. |
First page:
GET /external/api/v1/products/export?since=2024-01-01&limit=500
Subsequent pages (use the next_cursor from the previous response):
GET /external/api/v1/products/export?cursor=<opaque_token>&limit=500
Important: Do not send both
sinceandcursorin the same request. The first response fixes acutofftimestamp so the entire walk is a consistent snapshot. Sending both since and cursor returns a 422 error. The stocks export accepts only cursor and limit; any other parameter returns 422.
Stock export — request parameters
| Parameter | Required | Description |
|---|---|---|
cursor | No, omit on the first request | Opaque token from the previous response's next_cursor. |
limit | No | Records per page. Default: 100. Maximum: 1000. |
First page:
GET /external/api/v1/inventory/stocks/export?limit=1000
Subsequent pages:
GET /external/api/v1/inventory/stocks/export?cursor=<opaque_token>&limit=1000
Cursor export response structure
{
"data": [ ...records... ],
"next_cursor": "<opaque_token>",
"has_more": true,
"meta": {
"rows": 1000
}
}
Key fields:
has_more—truemeans more pages exist;falsemeans you have received all records. Stop when this isfalse.next_cursor— pass this as thecursorparameter on your next request.meta.rows— how many records are in this page'sdataarray.
Choosing the right approach
| Scenario | Recommended approach |
|---|---|
| Fetching filtered sales orders, POs, companies, etc. | Offset pagination (page / per_page) |
| Syncing your entire product catalog | Products export cursor pagination (since then cursor) |
| Syncing current stock levels for all SKUs | Stock export cursor pagination (cursor) |
| Ad-hoc queries with filters | Offset pagination with filter parameters |
Quick checklist
- Set
per_page=100(the maximum) on offset endpoints to minimize round trips. - Stop looping when
meta.current_pageequalsmeta.last_page(offset) orhas_moreisfalse(cursor). - On the products export, only send
sinceon the very first request; switch tocursorfor all subsequent pages. - Treat
next_cursoras an opaque string — do not parse or construct it manually.