HeyKoala Voice APIAPI

Pagination

How collections page, and how to walk one safely.

Every collection endpoint returns the same shape inside data:

{
  "count": 1287,
  "limit": 50,
  "offset": 0,
  "results": [ ... ]
}
  • count — total rows matching the query, ignoring limit and offset.
  • limit — how many came back. Defaults to 50, capped at 200. Ask for more and you silently get 200, so do not assume your requested page size.
  • offset — how many were skipped.

Walking a collection

curl "https://dvoice.heykoala.ai/api/v1/ext/contacts/?limit=200&offset=0" \
  -H "Authorization: Bearer hk_api_..."

Increment offset by the limit you actually received until you have count rows:

offset, rows = 0, []
while True:
    page = get(f"/contacts/?limit=200&offset={offset}")["data"]
    rows += page["results"]
    offset += page["limit"]
    if offset >= page["count"]:
        break

Offset paging drifts under writes

Offsets are positions, not bookmarks. If rows are inserted or deleted while you page, you can see a row twice or miss one — the classic symptom is a sync that is reliably a few records short.

If that matters:

  • Page newest-first and stop when you reach a record you already have, rather than walking to the end.
  • Or narrow the window with a filter — for call logs, a date range — so the set is not moving underneath you.
  • Or accept it and make your import idempotent by upserting on our id, which is stable.

Filtering

Most collections take filters as query parameters, listed on each endpoint in the reference. Two worth knowing:

  • GET /contacts/search/ treats any unrecognised query parameter as a field filter — first against contact fields, then against your organization's own custom fields. So ?city=Bangalore works if city is a custom field you use.
  • GET /webhooks/?call_id=... narrows the delivery log to one call, which is the fastest way to answer "did you actually send it?".

On this page