> ## Documentation Index
> Fetch the complete documentation index at: https://docs.meetdarwin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Pagination and syncing

> Page through Darwin API lists with limit, starting_after and next_cursor, and sync only what changed with updated_since and created_since.

Every list endpoint returns the same envelope and pages with a cursor. Cursors stay correct while new conversations arrive; page offsets would shift.

```json theme={null}
{
  "object": "list",
  "data": [ { "id": "conv_c-1760280000000-k3j9", "object": "conversation" } ],
  "has_more": true,
  "next_cursor": "eyJ0IjoiMjAyNi0xMC0xMlQwOTozMDowMC4wMDBaIiwiaWQiOiJjLTE3NjAyODAwMDAwMDAtazNqOSJ9"
}
```

| Parameter or field | Meaning |
| - | - |
| `limit` | How many items per page, 1 to 100. Default 25. |
| `starting_after` | The `next_cursor` from the previous page. Leave it out for the first page. |
| `has_more` | `true` when there is another page |
| `next_cursor` | Pass it as `starting_after` to get the next page; `null` on the last page |

Treat the cursor as opaque: pass it back exactly as you received it. A cursor that was edited or came from another list returns `400` [`invalid_request`](/api-reference/errors#invalid_request) with `param: "starting_after"`.

## Order

Lists are newest first. Conversations are ordered by when they started, or by when they last changed when you pass `updated_since`. Contacts and meetings are ordered by their last change. Events are ordered by when they happened.

## Read every page

<CodeGroup>
  ```javascript Node theme={null}
  async function* all(path, params = {}) {
    let cursor = null;
    do {
      const url = new URL(`https://api.meetdarwin.ai/v1${path}`);
      for (const [k, v] of Object.entries({ limit: 100, ...params })) url.searchParams.set(k, v);
      if (cursor) url.searchParams.set("starting_after", cursor);
      const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.DARWIN_API_KEY}` } });
      if (!res.ok) throw new Error(`${res.status} ${(await res.json()).code}`);
      const page = await res.json();
      yield* page.data;
      cursor = page.has_more ? page.next_cursor : null;
    } while (cursor);
  }

  for await (const meeting of all("/meetings", { verified: "true" })) console.log(meeting.id, meeting.start_at);
  ```

  ```python Python theme={null}
  import os, requests

  def all_items(path, **params):
      params = {"limit": 100, **params}
      while True:
          res = requests.get(
              f"https://api.meetdarwin.ai/v1{path}",
              params=params,
              headers={"Authorization": f"Bearer {os.environ['DARWIN_API_KEY']}"},
          )
          res.raise_for_status()
          page = res.json()
          yield from page["data"]
          if not page["has_more"]:
              return
          params["starting_after"] = page["next_cursor"]

  for meeting in all_items("/meetings", verified="true"):
      print(meeting["id"], meeting["start_at"])
  ```
</CodeGroup>

## Sync only what changed

A sync job should not read everything every time. Pass the time of your last run:

| List | Filter |
| - | - |
| `GET /conversations` | `updated_since` (a new message, a lead or a booking) or `created_since`. Darwin's grade arrives separately: use the `conversation.completed` webhook or `graded=true`. |
| `GET /contacts` | `updated_since` |
| `GET /meetings` | `updated_since` (booked, rescheduled or canceled) |
| `GET /events` | `created_since` (the last 30 days) |

Values are ISO 8601, for example `2026-10-01T00:00:00Z`. A value that isn't a date returns `400` with `param` naming the filter.

A safe loop:

1. Note the current time **before** you start reading. Call it `started`.
2. Read every page with `updated_since` set to the `started` time of your last successful run.
3. Upsert each item by its `id`. The same item can appear in two runs; that's expected.
4. Only when every page succeeded, save `started` for the next run.

Something that changes while you are paging moves to the front of the list and may be skipped in this run, but its new `updated_since` time is after `started`, so the next run picks it up. See the [CRM sync recipe](/api-reference/recipes#sync-leads-to-a-crm) for a full example.

## Lists without paging

`GET /webhook_endpoints` returns all of a workspace's endpoints (up to 16) in one page. `GET /chat_buttons` likewise returns every chat button.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.