Pagination
GET /sops and GET /assignments return pages of up to 100 items. Pass next_cursor back as cursor, with the same filters, until has_more is false.
On this page
Which endpoints are paginated
| Endpoint | Paginated | Order | Default limit |
Max limit |
|---|---|---|---|---|
GET /sops |
Yes, cursor | updated_at ascending (oldest change first), then id |
50 | 100 |
GET /assignments |
Yes, cursor | created_at descending (newest first), then id |
50 | 100 |
GET /search |
No, best matches only | Rank, best first | 20 | 50 |
GET /libraries |
No, everything in one response | See the endpoint | ||
GET /boards |
No, everything in one response | position |
||
GET /people |
No, everything in one response | Name |
Response shape
A paginated response has three top-level fields:
JSON
{
"data": [
{ "id": "5b7e9d1a-2c4f-4e6b-8a1d-3c5e7f9a0b03", "title": "New hire onboarding (example)" }
],
"has_more": true,
"next_cursor": "eyJ0cyI6IjIwMjYtMDktMThUMDk6NDE6MDcuMDA0NTEyWiIsImlkIjoiNWI3ZTlkMWEtMmM0Zi00ZTZiLThhMWQtM2M1ZTdmOWEwYjAzIn0"
}| Field | Type | Meaning |
|---|---|---|
data |
array | This page’s items. Can be []. |
has_more |
boolean | true when at least one more item exists after this page. |
next_cursor |
string or null | Pass as cursor to get the next page. null when has_more is false. |
Getting the next page
- Make the first request without
cursor. - If
has_moreistrue, repeat the request withcursorset tonext_cursorand the same filters (library_id,statusand so on). The cursor records a position in the ordering, not your filters, so changing filters mid-way gives you a page of a different list. - Stop when
has_moreisfalse.
Shell
curl "https://app.sopcompass.com/api/v1/sops?limit=100&cursor=eyJ0cyI6IjIwMjYtMDktMThUMDk6NDE6MDcuMDA0NTEyWiIsImlkIjoiNWI3ZTlkMWEtMmM0Zi00ZTZiLThhMWQtM2M1ZTdmOWEwYjAzIn0" \
-H "Authorization: Bearer $SOPCOMPASS_API_KEY"JavaScript
async function* allPages(path, params = {}) {
let cursor = null;
do {
const qs = new URLSearchParams({ ...params, limit: "100", ...(cursor ? { cursor } : {}) });
const res = await fetch(`https://app.sopcompass.com/api/v1${path}?${qs}`, {
headers: { Authorization: `Bearer ${process.env.SOPCOMPASS_API_KEY}` },
});
if (!res.ok) throw new Error(`${res.status} ${(await res.json()).error.code}`);
const page = await res.json();
yield* page.data;
cursor = page.has_more ? page.next_cursor : null;
} while (cursor);
}
for await (const sop of allPages("/sops", { include_archived: "true" })) {
console.log(sop.updated_at, sop.title);
}Python
import os
import requests
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['SOPCOMPASS_API_KEY']}"
def all_pages(path, **params):
cursor = None
while True:
query = {**params, "limit": 100}
if cursor:
query["cursor"] = cursor
res = session.get(f"https://app.sopcompass.com/api/v1{path}", params=query, timeout=30)
res.raise_for_status()
page = res.json()
yield from page["data"]
if not page["has_more"]:
break
cursor = page["next_cursor"]
for assignment in all_pages("/assignments", status="overdue"):
print(assignment["due_on"], assignment["assignee"]["email"], assignment["sop"]["title"])Cursor rules
- Cursors are opaque. Don’t decode, build or edit them; their format can change within v1. A cursor that can’t be read answers
422 invalid_cursor. - Cursors don’t expire. A cursor marks a position (the last item’s sort key), not a snapshot, so you can resume later.
- Ties are handled. Positions keep microsecond precision and break ties on
id, so items written in the same millisecond are neither skipped nor repeated. limitmust be a whole number from 1 to 100. Anything else answers422 invalid_parameter.
Items that change while you page
Because pages are positions in a live list:
GET /sopsis ordered byupdated_at. An SOP edited while you page moves to the end of the list, so you see it again on a later page with its new content. De-duplicate byidand keep the copy with the latestupdated_at. This is what makesupdated_sincesyncs complete: nothing that changes is ever missed.GET /assignmentsis newest first. Assignments created after your first request sort before your position, so they don’t appear in the pages that follow. Start a new pass from the first page to pick them up.