Incremental Sync
Documents change after they are issued — a receipt settles an invoice, a credit note reverses one, an allocation number arrives late. None of that is a request you made, so none of it comes back on a response. The change feed on GET /documents is how you pull those changes without re-reading everything.
Opening a Sweep
Pass updatedSince (any ISO 8601 timestamp) to start one. The listing switches from newest-issued-first to oldest-change-first — the only order a sweep can resume in without gaps — and the response gains nextCursor and hasMore.
GET /documents?updatedSince=2026-08-01T00:00:00Z&limit=200
{
"data": [
"..."
],
"total": 500,
"pagination": {
"total": 500,
"limit": 200,
"offset": 0,
"hasMore": true,
"page": 1,
"pageSize": 200,
"totalPages": 3
},
"nextCursor": "MTc3NDQyODAwMDAwMDo4NDIxMw",
"hasMore": true
}Continuing with a Cursor
Hand nextCursor back as cursor to fetch the next batch. The cursor is built from the last row you actually received, so paging stays stable no matter what changes underneath — unlike an offset, which describes a position in a set that is moving.
GET /documents?cursor=MTc3NDQyODAwMDAwMDo4NDIxMw&limit=200
Treat the cursor as opaque: it is a token to hand back, not a value to parse or construct. When both are sent, cursor wins over updatedSince. A short page means the feed is drained — store the cursor and poll again later instead of replaying what you just read.
Errors
A parameter that cannot be understood is rejected, never ignored — falling back to an ordinary listing would hand you a page of unrelated documents and let you believe you had synced.
| Code | Cause |
|---|---|
INVALID_CURSOR | The cursor was not one we issued. Pass nextCursor back verbatim. |
INVALID_UPDATED_SINCE | Not a valid ISO 8601 date. |
A Complete Sync Loop
// cursor is persisted between runs; on the very first run there is none.
let cursor = await store.get("glance:documents:cursor");
const params = cursor
? { cursor }
: { updatedSince: "2026-01-01T00:00:00Z" };
while (true) {
const res = await glance.documents.list({ ...params, limit: 200 });
for (const doc of res.data) upsert(doc); // keyed by visibleId
if (res.nextCursor) {
cursor = res.nextCursor;
await store.set("glance:documents:cursor", cursor);
params.cursor = cursor;
delete params.updatedSince;
}
if (!res.hasMore) break; // drained — resume from the stored cursor later
}visibleId and overwrite.