Skip to main content
List endpoints return results a page at a time. Every paginated list in the iBubbl API works the same way, so one loop covers all of them. List messages and List contacts follow these rules, and lists added later will too.

How it works

  1. Request the first page. Pass limit (1 to 100, default 50) and any filters.
  2. The response is {"data": [...], "has_more": true | false}.
  3. If has_more is true, request the next page with the same filters plus starting_after set to the id of the last item in data.
  4. Stop when has_more is false.
The next request adds starting_after=8b3a4e72-1c2d-4e5f-9a0b-2c3d4e5f6a7b.

The rules

  • Newest first. Items are ordered by created_at descending, then by id descending for items created in the same microsecond. The order is total: two requests never order the same items differently.
  • The cursor is an ID. starting_after is the id of an item you already have, not a page number or an opaque token. You can store it, log it and read it. It stays valid if you change filters: the next page starts after that item’s position, whether or not the item itself matches the new filters.
  • Pages don’t shift. Each page starts from the cursor item’s position, not from a count of rows. Items created while you page through can’t push an item onto two pages or make one disappear. New items appear at the front of the list, so a loop that started before they arrived doesn’t see them; run it again with created_after to pick them up.
  • has_more is exact. When it’s false there is nothing after the last item. A page can be shorter than limit only when it’s the last one, and an empty data always comes with has_more: false.
  • limit is strict. Outside 1–100 is 422 validation_error, not rounded to the nearest valid value.
  • Unknown parameters are refused. A query parameter the endpoint doesn’t know, or one given twice, returns 422 validation_error naming it. This is stricter than request bodies on purpose: a misspelt cursor such as startingAfter, quietly ignored, would return the first page forever and your loop would never end.
  • An unknown cursor is an error. A starting_after that isn’t one of your items’ IDs returns 422 invalid_cursor. Another account’s ID is answered the same way, and so is a contact deleted after you received it: start again from the first page.
  • Time filters are exclusive and need a time zone (on lists that have them, such as List messages). created_after and created_before exclude the exact time given. Write times as ISO 8601 with Z (2026-11-10T09:30:00Z); created_at values come back in that form, so you can pass them back unchanged. In a URL, an unencoded + (as in +00:00) reads as a space, so encode it as %2B or use Z.
  • Filters are applied before paging. A rare filter value still returns every match across pages. A filter that matches nothing returns an empty page, not 404.

Read every page

The loop below fetches page after page until has_more is false, and yields items one at a time. Pass any filters the endpoint takes. If it goes over the list rate limit, it waits as long as Retry-After says and asks for the same page again.
A few notes:
  • Keep the filters the same on every page, and change only starting_after. The loops above do this by building each request from the same filters.
  • Fetch pages one at a time. Each page needs the last ID of the one before it, so pages can’t be fetched in parallel.
  • A list request is safe to retry. On a network error, a timeout or a 5xx, request the same page again with the same starting_after. Listing never changes anything. Don’t retry a 4xx: fix the request. The one exception is 429 rate_limited: your account made more than 120 list requests in 60 seconds. Wait the number of seconds in the Retry-After header, then send the same request again. The refused request didn’t count, and the cursor is still valid. See Rate limits.
  • One budget per account. Every API key on your account and every list endpoint share the 120 requests a minute. Two loops running side by side each get about half.
  • Big ranges. Narrow the range with created_after / created_before rather than reading your whole history on every run. The reconciliation recipe shows how to keep a checkpoint.

Next

List messages

Every filter and the response shape.

List contacts

Search, tag, opt-out and block filters.

Recover from downtime

Catch up on what your webhook endpoint missed.