Get started

Pagination

Lists are paginated with opaque cursors: stable while data changes, and fast at any depth.

How it works

List endpoints return a page of items and a nextCursor. Pass that cursor back as cursor (in the query string, or the body for POST …/search) to get the next page. When nextCursor is null, you’ve reached the end.

  • limit sets the page size: 1 to 100, default 25 (conversation parts default to 50).
  • The first page of /contacts, /companies and /articles also includes total, the number of matches.
  • Cursors are opaque and tied to the list’s order and filters: send the same parameters with each cursor, and don’t build or edit cursors yourself.
  • Small collections — tags, ticket types, teams, teammates, data attributes, segments, collections — come back complete, with nextCursor: null.
GET /v1/contacts?role=user · 200 OK
{  "items": [    {      "object": "contact",      "id": "0199c512-6a7b-7c8d-9e0f-1a2b3c4d5e6f",      "role": "user",      "externalId": "usr_48213",      "email": "[email protected]",      "phone": "+49 30 1234567",      "name": "Lena Fischer",      "avatarUrl": null,      "ownerId": "0199a8f2-2d3e-7f4a-9b5c-6d7e8f9a0b1c",      "location": {        "city": "Berlin",        "region": "Berlin",        "country": "Germany",        "countryCode": "DE",        "timezone": "Europe/Berlin"      },      "language": "de",      "browser": "Chrome",      "os": "macOS",      "lastSeenAt": "2026-10-02T13:58:20.000Z",      "lastContactedAt": "2026-10-02T14:11:08.000Z",      "lastRepliedAt": "2026-10-02T14:05:31.000Z",      "signedUpAt": "2025-03-08T10:00:00.000Z",      "sessionCount": 214,      "unsubscribedFromEmails": false,      "blocked": false,      "customAttributes": {        "plan": "business",        "seats": 12,        "mrr": 1490      },      "tags": [        {          "id": "0199a8f3-5d6e-7f7a-8b8c-9d0e1f2a3b4c",          "name": "VIP"        }      ],      "companies": [        {          "id": "0199c514-2b3c-7d4e-9f5a-6b7c8d9e0f1a",          "name": "Northwind Analytics"        }      ],      "createdAt": "2026-09-14T09:12:44.000Z",      "updatedAt": "2026-10-02T16:40:03.000Z"    }  ],  "nextCursor": "WyIyMDI2LTEwLTAyVDE0OjA1OjMxLjAwMFoiLCIwMTk5YzUyMC03ZThmIl0",  "total": 1284}

Fetching every page

async function* listAll(path, params = {}) {  let cursor = null  do {    const query = new URLSearchParams({ ...params, limit: "100", ...(cursor ? { cursor } : {}) })    const res = await fetch(`https://api.rabbitblaze.com/v1${path}?${query}`, {      headers: { Authorization: `Bearer ${process.env.RABBITBLAZE_API_KEY}` },    })    if (!res.ok) throw new Error((await res.json()).error.message)    const page = await res.json()    yield* page.items    cursor = page.nextCursor  } while (cursor)} for await (const contact of listAll("/contacts", { role: "user" })) {  console.log(contact.email)}
import osimport requests session = requests.Session()session.headers["Authorization"] = f"Bearer {os.environ['RABBITBLAZE_API_KEY']}"  def list_all(path, **params):    cursor = None    while True:        query = {**params, "limit": 100, **({"cursor": cursor} if cursor else {})}        res = session.get(f"https://api.rabbitblaze.com/v1{path}", params=query)        res.raise_for_status()        page = res.json()        yield from page["items"]        cursor = page["nextCursor"]        if not cursor:            break  for contact in list_all("/contacts", role="user"):    print(contact["email"])

Syncing changes

To keep a copy of conversations up to date, list them with order=updated and updatedAfter set to your last sync time, or — better — subscribe to webhooks and only fetch what changed.