pagination: { limit, cursor?, hasMore } envelope so callers can iterate without page-counting math.
The pagination envelope
Endpoints that paginate
Every list endpoint acceptslimit (1–100) + cursor and returns the pagination envelope. The default limit is 100 except where noted.
| Endpoint | Default limit | Max limit | Notes |
|---|---|---|---|
POST /v1/contacts/search | 50 | 100 | The contact read. Filterable + sortable (search, sort, order, logic, filters, audienceId) in the JSON body. |
GET /v1/fields | 50 | 100 | Custom contact-field definitions for the brand. |
GET /v1/automations/runs | 25 | 100 | Filterable on automationId, triggerEventId, status, from, to. Logs are attached per run. |
GET /v1/audiences | 100 | 100 | Saved audiences for the brand. |
GET /v1/domains | 100 | 100 | All sending domains; ?sendableOnly=true for verified-only. |
GET /v1/emails | 100 | 100 | Latest version per emailId. Filterable on status, createdAtFrom/To, updatedAtFrom/To. |
GET /v1/templates | 100 | 100 | Public template catalog; each row is a full template (html + previewImage + title/category/brand). Filterable on brand, category, semantic. |
GET /v1/automations/triggers | 100 | 100 | Integration-provisioned + API-created triggers. |
GET /v1/automations | 100 | 100 | Latest version per automationId; lean by default (?automationId= + ?include=graph,versions for one). |
GET /v1/analytics/sends | 100 | 100 | Campaign sends + stats. Filterable on status, emailId, from, to; ?sendId= + ?include=events for one. |
GET /v1/analytics/campaigns | 100 | 100 | Lifetime per-campaign KPIs. |
GET /v1/analytics/events | 50 | 100 | Unified event explorer. Filterable on recipientEmail, eventType, automationId, from, to. |
GET /v1/analytics/trigger-instances | 100 | 100 | Fired-trigger audit log. Filterable on triggerEventId, from, to. |
Single-resource reads (identity in the query)
Reads are flat: there is no separate get-one path. To fetch one row, pass its id key to the resource’s list endpoint and readdata[0]. The
response is the same { data, pagination } envelope as the list —
just scoped to one row. ?include= opt-ins embed the heavier detail:
| Read | include opt-ins |
|---|---|
GET /v1/emails?emailId=eml_xxx (carries previewImage) | html, versions |
GET /v1/audiences?audienceId=aud_xxx | count |
GET /v1/automations?automationId=auto_xxx | graph, versions |
GET /v1/automations/runs?automationRunId=run_xxx | logs |
GET /v1/automations/triggers?triggerEventId=tri_xxx | — |
GET /v1/analytics/sends?sendId=snd_xxx | events |
GET /v1/analytics/trigger-instances?triggerInstanceId=tin_xxx | — |
GET /v1/domains?domainId=dom_xxx | — |
data array (and 404 <RESOURCE>_NOT_FOUND only on the path-based write endpoints, e.g.
PATCH /v1/automations/{automationId}). The contact read is
POST /v1/contacts/search — look one address up with a
{ field: 'email', operator: 'equals' } filter.
Canonical iteration loop
The standard cursor pattern — the contact read is aPOST with a JSON
body, so the cursor rides in the body (GET lists put cursor in the
query instead):
SDK pagination (TypeScript)
The official@brew.new/sdk returns the raw { data, pagination } shape — pass the cursor back on the next call:
brew.contacts.search → brew.automations.runs.list with the same shape (runs use a GET list, so its filters are query params).
Filter combinations
POST /v1/contacts/search takes search + sort + filter in one JSON body:
GET /v1/automations/runs supports time-range and status filters:
Cursor semantics
- Opaque. Cursors are server-generated tokens. Don’t parse them; don’t synthesise them. The format may change between releases.
- Stable within a page. A cursor returned on page N points to “the next batch of rows that existed when N was rendered”. New rows inserted concurrently may show up; deleted rows may be skipped. This is fine for analytics / bulk export; if you need strict snapshot reads, freeze a time bound with
?from=&to=. - 24-hour TTL. Cursors don’t expire on a strict clock today, but treat them as if they’re good for ~24h — re-start with no cursor if a job pauses overnight.
See also
- Rate limits — a tight pagination loop can burn through
100/minquickly; consider parallelizing across keys or honoringX-RateLimit-Remaining. - Batch operations — for writing lots of rows fast (
POST /v1/contactsaccepts up to 1000 rows per request). - Errors —
404on a get-one lookup;400 INVALID_REQUESTon bad cursors.
Need Help?
Our team is ready to support you at every step of your journey with Brew. Choose the option that works best for you:- Self-Service Tools
- Talk to Our Team
Search Documentation
Type in the “Ask any question” search bar at the top left to instantly find relevant documentation pages.
ChatGPT/Claude Integration
Click “Open in ChatGPT” at the top right of any page to analyze documentation with ChatGPT or Claude for deeper insights.