# Pagination and dates

List routes take `page`, from 1, and `limit`, and answer with `meta`:

**meta in GET /v1/callsCopy**

```json
{
  "meta": {
    "page": 2,
    "limit": 500,
    "total_pages": 4,
    "total_calls": 1873
  }
}
```

| Route | `limit` | Total in `meta` |
| --- | --- | --- |
| `GET /v1/calls` | 1 to 500, default 500 | `total_calls` |
| `GET /v1/messages` | 1 to 500, default 500 | `total_messages` |
| `GET /v1/faxes` | 1 to 500, default 500 | `total_faxes` |
| `GET /v1/contacts` | 1 to 500, default 100 | `total_contacts` |
| `GET /v1/webhooks/failures` | 1 to 100, default 20 | `total` |
| `GET /v1/callers/{number}` | 1 to 50 calls, default 10, no pages | `counts` |
| `GET /v1/messages/queue` | Up to 200 items, no pages | `queue.pending` |

## Dates and time zones

- `start_date` and `end_date` are `YYYY-MM-DD`, and you send both or neither. Neither, or only one, means today.
- They are read in `timezone`, an IANA name such as `America/Chicago`. The default is `America/New_York`. A name the API does not know falls back to New York, so check `query.timezone` in the answer.
- A request covers at most 366 days, and `end_date` may not come before `start_date`. Either answers `400`. For more history, walk a year at a time.
- The answer echoes what it used in `query`: the local dates and the time zone.
- Every time in an answer is UTC, except `created_at` in the send answers of `POST /v1/messages` and `POST /v1/faxes`, which carries New York time with its offset. Webhook endpoint, failure and queue listings write times as `2026-09-24 13:06:53`, also UTC.

## Stable order

Rows that tie on the sort key come back in a fixed order, so a page never repeats or skips a row while the data stands still. New calls land while you page, though. To walk a range cleanly, sort `day_asc`, or walk a window that has closed, such as yesterday.

**curl**

```bash
# Every page, oldest first, until page reaches meta.total_pages (uses jq)
page=1
while :; do
  body=$(curl -s -H "Authorization: Bearer $VOCATECH_API_KEY" \
    "https://api.vocatech.com/v1/messages?start_date=2026-09-01&end_date=2026-09-23&sort=day_asc&limit=500&page=$page")
  echo "$body" | jq -c '.messages[]'
  [ "$page" -ge "$(echo "$body" | jq '.meta.total_pages')" ] && break
  page=$((page + 1))
done
```

**Node**

```javascript
import { vt } from './vocatech.mjs';

// Walks every page of /v1/calls, /v1/messages or /v1/faxes, oldest first.
async function* walk(path, listKey, query = {}) {
  for (let page = 1; ; page++) {
    const qs = new URLSearchParams({ sort: 'day_asc', limit: '500', ...query, page: String(page) });
    const data = await vt('GET', `${path}?${qs}`);
    yield* data[listKey];
    if (page >= data.meta.total_pages) return;
  }
}

for await (const msg of walk('/v1/messages', 'messages', { start_date: '2026-09-01', end_date: '2026-09-23' })) {
  console.log(msg.sent_time, msg.direction, msg.remote_number);
}
```

**Python**

```python
from vocatech import vt


def walk(path, list_key, **query):
    """Every page of /v1/calls, /v1/messages or /v1/faxes, oldest first."""
    page = 1
    while True:
        data = vt("GET", path, params={"sort": "day_asc", "limit": 500, **query, "page": page})
        yield from data[list_key]
        if page >= data["meta"]["total_pages"]:
            return
        page += 1


for msg in walk("/v1/messages", "messages", start_date="2026-09-01", end_date="2026-09-23"):
    print(msg["sent_time"], msg["direction"], msg["remote_number"])
```

**PHP**

```php
<?php
require __DIR__ . '/vocatech.php';

// Every page of /v1/calls, /v1/messages or /v1/faxes, oldest first.
function vt_walk(string $path, string $listKey, array $query = []): Generator
{
    for ($page = 1; ; $page++) {
        $qs = http_build_query(array_merge(['sort' => 'day_asc', 'limit' => 500], $query, ['page' => $page]));
        $data = vt('GET', $path . '?' . $qs);
        yield from $data[$listKey];
        if ($page >= $data['meta']['total_pages']) {
            return;
        }
    }
}

foreach (vt_walk('/v1/messages', 'messages', ['start_date' => '2026-09-01', 'end_date' => '2026-09-23']) as $msg) {
    echo $msg['sent_time'], ' ', $msg['direction'], ' ', $msg['remote_number'], PHP_EOL;
}
```

For a nightly sync, walk yesterday in full each night. For a first import, walk a year at a time.
