Every collection endpoint answers with the same envelope and pages the same way. This guide walks a collection of documents from the first page to the last — two requests in total.
Before you begin
-
A few documents to page through. The example assumes three.
Step 1: Read the first page
Ask for the first page, two items at a time:
GET /documents?limit=2
Authorization: Bearer YOUR_API_KEY
HTTP/2 200
{
"pagination": {
"limit": 2,
"nextCursor": "e3b7a92c15d84f60c7a2b95e1d3f48a6b90c4e27d5f18a3c6e9b02d47f5a1c38",
"offset": 0
},
"total": 3,
...
}
The envelope is always the same three parts: results with the items (omitted here — they are full document objects), total with the overall count, and pagination describing where you are.
That leaves two ways to move through a collection, and this response offers both. offset and limit jump straight to a page, which suits shallow access — a page of results in your own interface, for instance. Walking the whole collection is the cursor's job: nextCursor is issued while you page from the start of the results — on the first page and then on every cursor page. Jumping in with an offset of your own leaves the walk behind, so those pages carry no cursor at all; pick one of the two approaches per traversal rather than mixing them.
A cursor is issued only for a page that came back full, which is how the walk ends: as soon as a page holds fewer items than the limit you asked for, nextCursor is null. A collection whose size is an exact multiple of the limit therefore hands you one more cursor and one final, empty page — so drive the loop off nextCursor rather than off the number of items you got.
Step 2: Follow the cursor
Pass the cursor back to get the next page:
GET /documents?cursor=e3b7a92c15d84f60c7a2b95e1d3f48a6b90c4e27d5f18a3c6e9b02d47f5a1c38
Authorization: Bearer YOUR_API_KEY
HTTP/2 200
{
"pagination": {
"limit": 2,
"nextCursor": null,
"offset": 2
},
"total": 3,
...
}
One document remains and nextCursor is null — you have reached the end. The offset field shows your position as a progress indicator.
A cursor must be the only parameter of the request — the filters are remembered from the first page — and it expires after a period of inactivity. Continuous paging is fine; a long pause means starting over.
Endpoints that always return their complete list in one response carry pagination: null instead. There is nothing to follow there, and total is the number of items you received.
Complete example
Collecting a whole collection, page by page, as a Node.js script. Error handling is minimal for brevity — in production, inspect the error envelope of non-2xx responses.
const BASE_URL = "https://sandbox.circularo.com/api/v1/public"; // your instance's base URL
const API_KEY = "YOUR_API_KEY";
const AUTH_HEADER = { "Authorization": `Bearer ${API_KEY}` };
const documents = [];
let query = "limit=2&category=Contract";
do {
const res = await fetch(`${BASE_URL}/documents?${query}`, { headers: AUTH_HEADER });
if (!res.ok) throw new Error(`Listing failed: ${res.status}`); // error handling kept minimal for brevity
const page = await res.json();
documents.push(...page.results);
console.log(`${documents.length} of ${page.total}`);
// from here on the cursor travels alone - it already carries the filters
query = (page.pagination.nextCursor === null) ? null : `cursor=${page.pagination.nextCursor}`;
} while (query !== null);
Next steps
-
Narrow a collection down before you page through it — Search documents.
-
Handle the errors a long walk can run into, including the rate limit — Handle errors.