Skip to content

Pagination ​

Every list endpoint pages the same way: with an opaque cursor, never an offset. An offset page shifts underneath you while rows are being written — the row at position 50 on one request is at 51 on the next — so a sync that pages by offset both repeats rows and misses them. A cursor names a place in the list, not a position, so neither happens.

Query parameters ​

ParameterDefaultMeaning
limit50Rows per page, 1–100.
cursor—The next_cursor from the previous page. Omit it to start.

The bounds are API.LIST in src/lib/const/api.const.ts.

An unknown query parameter is a 400, not ignored. A misspelt filter that was silently dropped would answer with every row, which is the worst possible way to discover the typo:

json
{
  "error": {
    "code": "INVALID_INPUT",
    "message": "Unknown query parameter `updated_after`.",
    "path": ["updated_after"]
  }
}

The response ​

json
{
  "data": [],
  "pagination": {
    "limit": 50,
    "has_more": true,
    "next_cursor": "WyIyMDI2LTEwLTA5VDEyOjAwOjAwLjEyMzQ1NloiLCIwMTk5YTFiMi1jM2Q0LTdlNWYtOGE5Yi0wYzFkMmUzZjRhNWIiXQ"
  }
}
FieldMeaning
limitThe page size this page was fetched with.
has_moreWhether another page follows. Loop on this.
next_cursorPass it as cursor for the next page. null exactly when has_more is false.

There is no total. Counting every matching row on every page costs more than the page itself on a large list, and a count is out of date by the time you read it anyway.

A cursor is opaque: do not decode it, build one, or store it long-term. It is valid only with the same filters it was issued under — send the same query string with a new cursor, nothing else changed. A cursor this API did not issue is a 400 on cursor.

A full read ​

bash
cursor=""
while :; do
  page=$(curl -sS "https://<your-app-domain>/api/public/v1/<list>?limit=100${cursor:+&cursor=$cursor}" \
    -H "authorization: Bearer $CALL_CENTER_API_KEY")

  echo "$page" | jq -c '.data[]'

  [ "$(echo "$page" | jq -r '.pagination.has_more')" = "true" ] || break
  cursor=$(echo "$page" | jq -r '.pagination.next_cursor')
done

Syncing changes: updated_since ​

A list that supports it — its page says so — takes updated_since, an ISO 8601 timestamp with an offset (2026-10-09T12:00:00Z, 2026-10-09T14:00:00+02:00). A timestamp with no offset is a 400, because it would be read in nobody's time zone. Such a list returns rows changed at or after that instant, oldest change first.

To keep a copy in step:

  1. Read every page with updated_since set to your last checkpoint.
  2. Upsert each row by id.
  3. Take the largest updated_at you saw as the next checkpoint, minus a minute.

The minute of overlap is deliberate. A change is stamped when it is made, but becomes visible when its transaction commits, so a change made just before your read can appear just after it with an earlier timestamp than rows you already have. Overlapping the window, and upserting by id, means a late commit is read next time instead of never.

Paging oldest change first also means a row updated while you are paging moves to the end of the list, where you will meet it again — it is never skipped.