Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.brew.new/llms.txt

Use this file to discover all available pages before exploring further.

Brew list endpoints use opaque cursor pagination. Every paginated response carries a pagination: { limit, cursor?, hasMore } envelope so callers can iterate without page-counting math.

The pagination envelope

type Pagination = {
  limit: number          // page size used for this response
  cursor?: string        // opaque token — pass back as `?cursor=…` to get the next page
  hasMore: boolean       // true when more rows exist
}
Where it appears (read mode):
type ListResponse<Row> = {
  [collectionName: string]: Array<Row>     // e.g. `contacts`, `runs`
  pagination?: Pagination
}

Endpoints that paginate

EndpointDefault limitMax limitCursorNotes
GET /v1/contacts50100YesFilterable + sortable (search, sort, order, logic, filters).
GET /v1/automation/runs25100YesFilterable on automationId, triggerEventId, status, mode, from, to. ?include=logs attaches per-node logs.

Endpoints that don’t paginate (yet)

These return the full set for the brand. Practical limits ship below; if you have a brand that consistently exceeds them, contact support and we’ll add cursor pagination.
EndpointPractical capWhy
GET /v1/audiences~hundredsSaved audiences are managed in the dashboard; brands typically have ≤ 100.
GET /v1/domains~tensEach brand verifies a handful of sending domains.
GET /v1/fields~hundredsCustom contact field definitions; usually ≤ 50.
GET /v1/emails~thousandsReturns the latest version per emailId. Filterable on emailType, status, createdAtFrom/To, updatedAtFrom/To.
GET /v1/templates~thousandsPublic template catalog; filterable on brand, category, semantic.
GET /v1/triggers~hundredsIncludes integration-provisioned + API-created triggers.
GET /v1/automations~hundredsReturns latest version per automationId.

Single-row lookups (always a one-element list)

Several get-one endpoints return the same envelope as the list — a one-element array — so SDK code stays uniform whether you’re fetching a single row or many:
EndpointBody
GET /v1/triggers?triggerEventId=tri_xxx{ triggers: [TriggerRow] }
GET /v1/automations?automationId=auto_xxx{ automations: [AutomationRow] } (optionally automations[0].versions[] with ?include=versions)
GET /v1/automation/runs?automationRunId=run_xxx{ runs: [AutomationRunRow], logs?: [...] } (optional logs[] with ?include=logs)
When a single-row lookup misses, you get 404 <RESOURCE>_NOT_FOUND — never an empty array.

Canonical iteration loop

The standard cursor pattern — works for contacts and automation runs:
async function* paginateAllContacts() {
  let cursor: string | undefined
  while (true) {
    const url = new URL('https://brew.new/api/v1/contacts')
    url.searchParams.set('limit', '100')
    if (cursor) url.searchParams.set('cursor', cursor)

    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.BREW_API_KEY!}` },
    })
    if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`)
    const body = (await res.json()) as {
      contacts: Array<{ email: string }>
      pagination: { limit: number; cursor?: string; hasMore: boolean }
    }

    for (const contact of body.contacts) yield contact
    if (!body.pagination.hasMore) return
    cursor = body.pagination.cursor
  }
}

for await (const contact of paginateAllContacts()) {
  console.log(contact.email)
}

SDK pagination (TypeScript)

The official @brew.new/sdk returns the raw { contacts, pagination } shape — pass the cursor back on the next call:
import { createBrewClient } from '@brew.new/sdk'

const brew = createBrewClient({ apiKey: process.env.BREW_API_KEY! })

let cursor: string | undefined
do {
  const page = await brew.contacts.list({ limit: 100, cursor })
  for (const contact of page.contacts) {
    console.log(contact.email)
  }
  cursor = page.pagination?.hasMore ? page.pagination.cursor : undefined
} while (cursor)
For automation runs, swap brew.contacts.listbrew.automationRuns.list with the same shape.

Filter combinations

GET /v1/contacts supports search + sort + filter on the same call:
GET /v1/contacts?search=jane&sort=createdAt&order=desc&limit=50
GET /v1/contacts?logic=and&filters=[{"field":"plan","operator":"eq","value":"pro"}]&limit=100
GET /v1/automation/runs supports time-range and status filters:
GET /v1/automation/runs?automationId=auto_abc&status=completed&from=2026-04-01T00:00:00.000Z&to=2026-04-08T00:00:00.000Z&limit=100&include=logs
See the per-endpoint pages under Public API v1 for the full filter parameter table.

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/min quickly; consider parallelizing across keys or honoring X-RateLimit-Remaining.
  • Batch operations — for writing lots of rows fast (POST /v1/contacts accepts up to 1000 rows per request).
  • Errors404 on a get-one lookup; 400 INVALID_REQUEST on 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:

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.