Current Client Surface
The TypeScript SDK is resource-oriented.Resources and Methods
A client is pinned to one brand at a time. A brand-scoped key resolves its
own, so you pass nothing. An organization-scoped key names one: set
brandId in the client config, or call brew.withBrand(brandId) for a
client that differs only in the brand it acts on. Either way the SDK sends
it as the X-Brand-Id header, never as a brandId body field.
brands, templates, flows, usage, and apiKeys are
organization-level and never send that pin. See
Organization and brand scope
and API authentication for the
complete scoping and permission contract.
Every collection has two reads. list(query) pages the collection;
get(id) returns the bare row, the same object a write returns. The id is
a positional argument, never a query filter, and include is a
detail-read option that embeds heavier fields:
These reads also take
include, outside the detail-read pattern:
Passing an id as a list filter now returns
400. If you are migrating
from a list({ emailId }) call that read data[0], see the
v1 migration guide.
One read sits outside that shape: brew.contacts.search({ filters, audienceId?, search?, sort, count?, include?, cursor }) stays for filter
bodies richer than the list() query, and brew.contacts.get(email) is the
single-address read. include: ['openProfile'] on a search attaches each
contact’s open-time profile and caps the page at 10 contacts.
The public SDK surface is deterministic-only: brew.automations
and brew.automations.triggers do not expose AI authoring methods. AI
body generation is still available on brew.emails.generate({ prompt });
chain it with brew.automations.create({ … }) to assemble automations
programmatically. To ingest existing markup as an editable design, use
brew.emails.import({ format, content }).
Paginated list and search methods use the standard { data, pagination }
envelope. See Pagination for cursor
semantics. Manual-audience run history joined that shape in v1:
brew.automations.audienceRuns.list({ automationId?, status?, limit?, cursor? })
now pages like every other list instead of capping at limit.
sends.listAll, automations.triggerInstances.listAll,
contacts.searchAll, analytics.eventsAll, chats.listAll, and
notifications.listAll are async iterators that page through the whole
result set for you via the shared autoPaginate helper, and
emails.comments.listAllMessages walks one comment thread back to its first
message. Those are the SDK’s auto-pagers; every other list is paged by hand
with cursor. notifications.listAll keeps going through short and empty
pages, because a notifications page can hold fewer rows than limit while
hasMore is true.
The SDK exposes the full strictly typed manual-audience lifecycle:
brew.brand.get() is read-only. It returns the brand the client acts on
(the key’s own brand, or the pinned one) plus its extraction readiness.
brew.brands is different: it lists, creates, and polls brands across the
organization.
brew.help.get() hits GET /v1/help, a no-auth, machine-readable
catalog (auth, scopes, rate limits, per-operation credit metering, the error
envelope, and the full endpoint list) any MCP server or agent can parse
to self-discover the API.
Common Flow: Trigger → Emails → Automation → Publish → Fire
End-to-end deterministic recipe: create a custom trigger, mint each email body in parallel, assemble the graph referencing thoseemailIds,
publish, and fire. Every step returns a typed result.
The subject and previewText below carry merge tags,
resolved against the trigger payload when the event fires.
Reading Sends and Trigger Instances
Sends read and write at their own root. The write isbrew.emails.send(input) (pass test: true for a one-off QA send), and
the reads are brew.sends.list(query) and
brew.sends.get(sendId, { include? }). Nothing about a send lives under
brew.analytics any more, and analytics keeps exactly three reports:
overview, automations, and the unified events feed.
Fired-trigger instances moved the same way, from analytics to
brew.automations.triggerInstances.
queued, scheduled, running, paused,
completed, partially_completed, failed, canceled. The old sent,
partially_sent and sending spellings are gone, and a status filter
typed against them returns 400.
AutomationNodeInput: A Per-Kind Discriminated Union
AutomationNodeInput is a discriminated union by type; setting
node.type narrows node.config automatically. The five node kinds
map 1:1 to the server-side Zod schemas:
Each
sendEmail node’s subject / previewText support
{{ variable | fallback }} interpolation against the trigger payload.
Source of Truth
The SDK follows the Brew OpenAPI contract. If you want the raw HTTP shape behind any method, use the API reference in this docs site.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 explore it further with ChatGPT or Claude.