Pagination
List endpoints that support cursor pagination accept an opaque ?cursor= query parameter —
never a page number. New integrations should use cursor exclusively.
Cursor format
A cursor is an opaque, base64url-encoded token. Treat it as a black box — don't decode it, don't construct one by hand, don't assume its format is stable across releases. Pass back exactly what the API gave you.
# First pagecurl -sS "https://api.owlmar.com/v1/maintenance/vessel/$VESSEL_ID?limit=25" \-H "Authorization: Bearer $OWLMAR_API_KEY"# Response includes pagination.cursor — pass it back verbatim for the next pagecurl -sS "https://api.owlmar.com/v1/maintenance/vessel/$VESSEL_ID?limit=25&cursor=eyJpZCI6Ii4uLiJ9" \-H "Authorization: Bearer $OWLMAR_API_KEY"
hasMore / total
{"data": [ /* up to `limit` rows */ ],"pagination": { "cursor": "eyJpZCI6Ii4uLiJ9", "limit": 25, "hasMore": true, "total": 143 }}
hasMore—trueif there's another page. Whenfalse,cursorisnull— stop paging.total— the full filtered row count, not just the current page. Safe to show in a UI ("143 results") without fetching every page first.limit— defaults to 25, maximum 100.
Cursors degrade gracefully. If the row a cursor pointed at was deleted between requests, pagination silently resumes from approximately where it left off — you won't get a "cursor invalid" error, and you won't skip or repeat an entire page the way an offset-based scheme can under concurrent writes.
The 3 known-gap endpoints
Not every list endpoint has cursor support yet — three are offset-only for structural reasons, not oversights:
| Endpoint | Why |
|---|---|
GET /v1/vessels | Computing the multi-vessel downgrade-lock annotation (isLocked) needs the full owned-vessel set, not one page at a time. Also a deliberately bare array for backward compatibility. |
GET /v1/vessels/:id/team | Same structural reason — the downgrade-lock computation needs the full crew roster. Also a genuinely bounded list, not a growing log. |
GET /v1/compliance/vessel/:vesselId/drills/cadences | Not a list at the top level ({ data: { cadences, overdue, dueSoon } }) and bounded by ISM drill-type count — cursor pagination doesn't apply. |
A handful of other endpoints have narrower gaps on specific query branches only (hybrid search, a couple of alternate sort orders) — each endpoint's own reference page documents exactly which branch supports cursor.
?page= sunset
Some list endpoints still accept legacy ?page=&limit= for backward compatibility. New
integrations should not use it. Using it triggers deprecation headers on the response:
Sunset: Sat, 30 Jan 2027 00:00:00 GMT Deprecation: true
Sunset date: 2027-01-30 — minimum 12 months' notice from the day the header first
appeared, per this API's versioning policy. After that date, ?page= support may be removed
from a given endpoint. ?cursor= is not going anywhere.
If you're paging through more than one screenful of results today with ?page=, migrate to
?cursor= now rather than waiting for the sunset date — the response shape difference is
small (an opaque cursor string instead of a page number) but touches your loop's stop
condition.
See Fleet Expense Reconciliation for a full
worked example of paging through a large result set with ?cursor=.
