pagination.md
Pagination
How MachineMail list endpoints page with limit, page_token and next_page_token: walking every page, sort order, and the endpoints that page differently.
Source: https://machinemail.dev/docs/reference/pagination
List endpoints page with three query parameters and return three fields, the same as AgentMail.
| Query parameter | Type | Default | Meaning |
|---|---|---|---|
limit |
integer | 50 |
Items per page. Values above 500 are capped at 500. A missing value, 0 or a negative number means 50; decimals are rounded down. A non-number returns 400 limit must be a number. |
page_token |
string | none | The next_page_token from the previous page. |
ascending |
boolean | false |
true for oldest first. Most lists are newest first by default. |
{
"count": 50,
"limit": 50,
"next_page_token": "eyJvIjo1MH0",
"inboxes": [ ... ]
}
| Field | Meaning |
|---|---|
count |
Number of items in this page (not a total). |
limit |
The limit that was applied. |
next_page_token |
Present only when there is another page. Pass it as page_token to get it. |
truncated |
Present and true on searches that hit their scan cap (see below). |
The items are under a key named after the resource: inboxes, messages, threads, drafts, webhooks, deliveries, domains, pods, api_keys, entries, suppressions, events, agent_registrations.
Walking every page
async function listAll(path: string, key: string) {
const items = [];
let pageToken: string | undefined;
do {
const url = new URL(`https://api.machinemail.dev${path}`);
url.searchParams.set('limit', '500');
if (pageToken) url.searchParams.set('page_token', pageToken);
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.MACHINEMAIL_API_KEY}` } });
const page = await res.json();
items.push(...page[key]);
pageToken = page.next_page_token;
} while (pageToken);
return items;
}
const inboxes = await listAll('/v0/inboxes', 'inboxes');
Page tokens
Treat tokens as opaque. Today a token encodes an offset into the result set, so:
- Items created while you page can shift later pages: an item can appear twice or be skipped. For a stable feed of changes, read the event log with
after=<event_id>instead. - A token that cannot be decoded is ignored and the list starts again from the first page. It is not an error.
- Use a token with the same filters and sort order that produced it.
Sort order
| List | Sorted by | Default |
|---|---|---|
| Messages, threads | message or thread timestamp |
newest first |
| Allow and block list entries | the entry value |
descending (Z to A); ascending=true for A to Z |
| Everything else | created_at |
newest first |
Events with after |
event order | oldest first |
Endpoints that page differently
| Endpoint | Behaviour |
|---|---|
GET /v0/api-keys |
Returns up to limit non-revoked keys (from the newest 500). limit defaults to 50 and is not capped. Never returns next_page_token; page_token and ascending are ignored. |
GET /v0/webhooks with a pod or inbox key |
Returns every webhook in scope (up to 500) in one response, with no next_page_token. |
GET /v0/pods with a pod key |
Returns only that pod. |
GET /v0/inboxes/search |
Scans the newest 500 inboxes. Returns count, inboxes and truncated: true when there were more. No paging. |
Thread search (q on thread lists, /threads/search) |
Scans the newest 500 matching messages. truncated: true when it hit the cap. |
POST .../messages/batch-get, batch-update |
Not paged. At most 100 IDs; limit in the response is the number of IDs you sent. |
GET /v0/events |
after takes an event ID (evt_...) or an ISO 8601 time and switches the default to oldest first: a cursor you can poll. |
GET /v0/metrics/* |
Not paged. limit trims the result. |
GET /v0/threads/{thread_id} |
Not paged. Returns up to 500 messages. |
| MCP tools | limit is 1 to 100, default 20 (list_inboxes uses the API default of 50). |