> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.jelliu.co/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.jelliu.co/_mcp/server.

# Pagination

Jelliu uses three paging styles, depending on the endpoint. Each is described below with its parameter names and bounds.

| Style                                 | Parameters        | Used by                                                                                                                                                           |
| ------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Timestamp cursor](#timestamp-cursor) | `limit`, `cursor` | `GET /api/agents`, `GET /api/calls`                                                                                                                               |
| [Page number](#page-number)           | `page`, `limit`   | `GET /api/campaigns`, `GET /api/campaigns/{campaignId}/contacts`, `GET /api/contacts`                                                                             |
| [Offset](#offset)                     | `limit`, `offset` | `GET /api/widgets`, `GET /api/integrations`, `GET /api/mcp-servers`, `GET /api/exports`, `GET /api/agents/{agentId}/knowledge`, `GET /api/agents/{agentId}/tools` |

Parameters are query-string values. A value out of range is rejected with `400 VALIDATION_FAILED` rather than silently clamped. See [Errors](/errors).

## Timestamp cursor

The cursor is an ISO 8601 timestamp: the `created_at` of the last item you received. The next page returns items created **before** it.

### Agents

**`limit`** `integer`

1 to 200. Defaults to 50.

---

**`cursor`** `string (ISO 8601 date-time)`

Return agents created before this instant. Must be a UTC timestamp ending in `Z`, for example `2026-08-01T15:04:05.000Z`.

---

**`search`** `string`

Case-insensitive match on the agent name. 1 to 200 characters.

---

`GET /api/agents` returns `{ "data": [ ... ] }` with no cursor field. To fetch the next page, pass the `created_at` of the last agent as `cursor`. When a page has fewer items than `limit`, you have reached the end.

```bash
curl -sS "https://api.jelliu.co/api/agents?limit=50&cursor=2026-08-01T15:04:05.000Z" \
  -H "Authorization: Bearer $JELLIU_API_KEY"
```

### Calls

**`limit`** `integer`

1 to 100. Defaults to 50.

---

**`cursor`** `string`

The `nextCursor` value from the previous page.

---

**`offset`** `integer`

0 to 100000. Alternative to `cursor` for numbered pages. A non-zero `offset` takes precedence over `cursor`.

---

**`agentId`** `string (uuid)`

Only calls handled by this agent.

---

**`campaignId`** `string (uuid)`

Only calls from this campaign.

---

`GET /api/calls` returns the page together with its paging state:

```json
{
  "data": {
    "calls": [ { "id": "...", "created_at": "..." } ],
    "total": 1284,
    "limit": 50,
    "offset": 0,
    "nextCursor": "2026-09-10T18:22:41.512Z"
  }
}
```

`nextCursor` is `null` when the page holds fewer than `limit` calls. Pass it back as `cursor` to continue:

**`Node.js`**

```javascript title="Node.js"
async function* allCalls(apiKey) {
  let cursor = null;
  do {
    const url = new URL('https://api.jelliu.co/api/calls');
    url.searchParams.set('limit', '100');
    if (cursor) url.searchParams.set('cursor', cursor);

    const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
    const { data } = await res.json();
    yield* data.calls;
    cursor = data.nextCursor;
  } while (cursor);
}
```

**`Python`**

```python title="Python"
import requests

def all_calls(api_key):
    cursor = None
    while True:
        params = {"limit": 100}
        if cursor:
            params["cursor"] = cursor
        res = requests.get(
            "https://api.jelliu.co/api/calls",
            params=params,
            headers={"Authorization": f"Bearer {api_key}"},
            timeout=30,
        )
        data = res.json()["data"]
        yield from data["calls"]
        cursor = data["nextCursor"]
        if not cursor:
            break
```

`/api/calls` has a stricter rate limit of 20 requests per minute. Use `limit=100` when walking the full history. See [Rate limits](/rate-limits).

## Page number

**`page`** `integer`

1-based page number. 1 to 100000. Defaults to 1.

---

**`limit`** `integer`

1 to 100. Defaults to **20** for campaigns and **50** for contacts.

---

Extra filters:

* `GET /api/campaigns/{campaignId}/contacts` accepts `status`.
* `GET /api/contacts` accepts `search` (1 to 200 characters) over phone, name and email.

Response shapes:

| Endpoint                                   | Shape                                                                     |
| ------------------------------------------ | ------------------------------------------------------------------------- |
| `GET /api/campaigns`                       | `{ "data": [ ... ], "meta": { "page": 1, "limit": 20 } }`                 |
| `GET /api/campaigns/{campaignId}/contacts` | `{ "data": { "contacts": [ ... ], "total": 0, "page": 1, "limit": 50 } }` |
| `GET /api/contacts`                        | `{ "data": { "contacts": [ ... ], "total": 0, "page": 1, "limit": 50 } }` |

Contact lists report `total`, so you can compute the number of pages. The campaign list does not; stop when a page returns fewer than `limit` items.

## Offset

**`limit`** `integer`

1 to 100. Defaults to 50.

---

**`offset`** `integer`

Number of items to skip. 0 to 100000. Defaults to 0.

---

```bash
curl -sS "https://api.jelliu.co/api/widgets?limit=50&offset=50" \
  -H "Authorization: Bearer $JELLIU_API_KEY"
```

Stop when a page returns fewer than `limit` items.

## Endpoints without paging

Some lists are small and take no paging parameters. For example, `GET /api/webhooks` returns up to 100 webhooks, newest first. `GET /api/webhooks/{webhookId}/delivery-logs` accepts only `limit` (1 to 50, default 20) and returns the most recent delivery attempts.