Pagination
Cursor pagination for list endpoints — limit, starting_after, ending_before and has_more.
List endpoints (GET /orders, GET /products, GET /catalog/products, GET /events, …) return a list object and use cursor pagination:
{
"object": "list",
"data": [ { "object": "order", "id": "ord_01k8x4b2q9w7e3r5t1y8u6i0pa" } ],
"has_more": true,
"url": "/api/v1/orders"
}| Parameter | Meaning |
|---|---|
limit |
Objects per page, 1–100 (default 20). |
starting_after |
An object id: return the page after it (the next page). Use the id of the last object you received. |
ending_before |
An object id: return the page before it (the previous page). Use the id of the first object of the current page. |
Use one cursor at a time (400 parameter_invalid if you send both). has_more tells you whether another page exists in the direction you are paging. Lists are newest first (except order events, which are oldest first). Filters (for example status on orders) combine with cursors — keep the same filters while paging.
Query parameters are strict: a parameter an endpoint does not document is rejected with 400 parameter_invalid naming it (Received unknown parameter: updated_since (query parameter).), so a misspelled filter never silently returns everything. The one exception is _, which every endpoint accepts and ignores (for cache-busters added by HTTP tools).
Cursors
A cursor is the id of an object of the same list, so it carries the list's id prefix:
| List | Cursor prefix |
|---|---|
GET /orders |
ord_ |
GET /products |
prod_ |
GET /catalog/products |
cp_ |
GET /files |
file_ |
GET /events |
evt_ |
GET /wallet/transactions |
txn_ |
GET /topups |
top_ |
GET /webhook_endpoints |
we_ |
A cursor with another prefix (for example a product id on /orders) is a 400 parameter_invalid on starting_after / ending_before. A well-formed id that does not exist (an object deleted since) is accepted: the page simply starts after (or ends before) where that id would sort. The catalog is the exception — its order is curated (featured first), so /catalog/products cursors must be ids of products in the catalog.
Iterate over everything
async function* listAll(path, params = {}) {
let startingAfter;
while (true) {
const query = new URLSearchParams({ ...params, limit: "100", ...(startingAfter ? { starting_after: startingAfter } : {}) });
const res = await fetch(`https://swaglists.com/api/v1${path}?${query}`, { headers: { Authorization: `Bearer ${process.env.API_KEY}` } });
const page = await res.json();
if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`);
yield* page.data;
if (!page.has_more || page.data.length === 0) return;
startingAfter = page.data.at(-1).id;
}
}
for await (const order of listAll("/orders", { status: "on_hold" })) {
console.log(order.id, order.hold_reason);
}def list_all(path, **params):
starting_after = None
while True:
query = {**params, "limit": 100}
if starting_after:
query["starting_after"] = starting_after
res = requests.get("https://swaglists.com/api/v1" + path, params=query, headers={"Authorization": f"Bearer {os.environ['API_KEY']}"})
res.raise_for_status()
page = res.json()
yield from page["data"]
if not page["has_more"] or not page["data"]:
return
starting_after = page["data"][-1]["id"]
for order in list_all("/orders", status="on_hold"):
print(order["id"], order["hold_reason"])curl "https://swaglists.com/api/v1/orders?limit=100" -H "Authorization: Bearer $API_KEY"
# next page:
curl "https://swaglists.com/api/v1/orders?limit=100&starting_after=ord_01k8x4b2q9w7e3r5t1y8u6i0pa" -H "Authorization: Bearer $API_KEY"Polling for new objects
To fetch only what is new since your last sync, page backwards from the newest object you already have: GET /events?ending_before=<newest event id you processed> returns newer events. Or filter by time: GET /orders?created[gte]=2026-10-20T00:00:00Z (see time filters). For near-real-time updates, prefer webhooks.
Time filters
created[gte] (at or after) and created[lte] (at or before) on GET /orders and GET /events accept:
| Format | Example | Meaning |
|---|---|---|
| Unix seconds | 1761955200 |
Seconds since 1970-01-01 UTC (fractions allowed). |
| Unix milliseconds | 1761955200000 |
Any number above 100000000000 is read as milliseconds. |
| ISO-8601 date | 2026-10-01 |
Midnight UTC of that day. |
| ISO-8601 date-time with an offset | 2026-10-01T09:30:00Z, 2026-10-01T11:30:00+02:00 |
That instant. |
| ISO-8601 date-time without an offset | 2026-10-01T09:30:00 |
Read as UTC — never as the server's or your local time. |
URL-encode + offsets (%2B02:00); an unencoded + (which query strings turn into a space) is understood as well. Anything else — other formats, impossible dates such as 2026-02-30, years outside 2000–2100 — is a 400 parameter_invalid on the parameter.
Full collections
A few endpoints return a complete collection in the same list shape with has_more: false and no paging: GET /catalog/categories, GET /products/{id}/variants, GET /orders/{id}/events and POST /shipping/rates.