> ## 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.

# Typed Payload Contracts

> Generate TypeScript contracts for trigger and transactional payloads, gate drift in CI, and verify live fires against the contract in Brew.

Every payload you send to Brew has a contract. A custom trigger declares one up front (its `payloadSchema`); a transactional email derives one from the pinned template (every `trigger.*` path the design reads). This guide turns those contracts into TypeScript types in your codebase, keeps them from drifting, and shows where Brew checks real fires against them.

<Note>
  Nested payload values (objects and arrays) require a Liquid-enabled workspace. The same nested shape sent to a legacy workspace is rejected with `400`. See [Merge tags and variables](/create-emails/merge-tags).
</Note>

## Generate types with brew-cli

`brew-cli types` writes one file with a type per contract (the command needs a key with the `automations` scope; `--transaction` also needs `sends`):

```bash theme={null}
brew-cli types --out src/brew-contracts.ts --transaction txn_8fK2mQ4pLx
```

Triggers are included automatically (every page of the workspace list — nothing is truncated). Transactional objects are opt-in by id because their contracts derive from each pinned template. The output is deterministic, with a content hash in the header:

```ts theme={null}
// brew:contracts sha256:6f1c…
/** Fire: POST /v1/automations/triggers/tri_signup/fire — body { payload: UserSignedUpPayload } */
export type UserSignedUpPayload = {
  email: string
  seats?: number
}

/** Fire: POST /v1/sends — body { transactionId: "txn_8fK2mQ4pLx", to, payload: OrderReceiptPayload } */
export type OrderReceiptPayload = {
  order: {
    total: number
    items: Array<{
      name: string
      qty?: number
    }>
  }
  note?: string
}
```

Type names follow one rule everywhere (the in-app Copy as TypeScript, the SKILL.md brief, and this file): PascalCase of the trigger title or the transactional subject, plus `Payload`. Renaming a subject renames the type, which is exactly the drift `--check` exists to catch; colliding names get the object id appended so the file always compiles. A template reference with no type evidence (no fallback, no numeric or boolean usage) is emitted as `unknown` for you to refine, never silently asserted as `string`.

Field optionality follows each plane's rules. Trigger fields are optional when `required: false`. Transactional fields are optional when the template gives them a `| default:` fallback; a path with no fallback fails strict fires when the caller omits it, so it is emitted as required.

In CI, gate drift with:

```bash theme={null}
brew-cli types --check
```

The command exits `1` when the workspace's contracts no longer match the committed file, which is the signal to regenerate and review the diff. A trigger schema edit or a transactional design change shows up as a type change in code review instead of a runtime surprise.

## Pin the types in SDK calls

`fire` and `send` accept a payload type parameter, so call sites compile against the contract:

```ts theme={null}
import Brew from '@brew.new/sdk'
import type { OrderReceiptPayload, UserSignedUpPayload } from './brew-contracts'

const brew = new Brew({ apiKey: process.env.BREW_API_KEY })

await brew.automations.triggers.fire<UserSignedUpPayload>({
  triggerEventId: 'tri_signup',
  payload: { email: 'jane@example.com', seats: 3 },
})

await brew.emails.send<OrderReceiptPayload>({
  transactionId: 'txn_8fK2mQ4pLx',
  to: 'jane@example.com',
  payload: { order: { total: 42, items: [{ name: 'Beans' }] } },
})
```

A missing required field or a wrong scalar type is a compile error. The wire request is unchanged; the generics are type-level only.

## Fetch the wiring brief for an agent

Both reads accept `?include=skill`, which adds a `skill` field: a complete SKILL.md-shaped brief (endpoint, auth, the typed contract inline, copy-paste snippets, and a test loop) that a coding agent can follow to wire your service.

```bash theme={null}
curl "https://brew.new/api/v1/transactional/txn_8fK2mQ4pLx?include=skill" \
  -H "Authorization: Bearer brew_your_api_key"

curl "https://brew.new/api/v1/automations/triggers?triggerEventId=tri_signup&include=skill" \
  -H "Authorization: Bearer brew_your_api_key"
```

Save the field as `SKILL.md` in your repo, or hand the URL to an agent. The in-app contract panel offers the same file under **Copy as → Download SKILL.md**, and it is generated from the same source as the API response.

## Preflight before the first fire

`GET` on the fire endpoint verifies the exact credential you will use, without firing:

```bash theme={null}
brew-cli automations triggers ready tri_signup
```

A `200` with `status: "ready"` means the key, brand scope, and permissions all pass, and `details` carries the payload contract plus what a fire would start. `counts.automations: 0` means fires are accepted and logged but start no runs until an automation wired to the trigger is published.

## Verify live fires in the app

Brew checks what your service actually sent against the contract:

* The transactional object page shows **Recent fires vs contract**: each retained fire's raw payload, with chips for required fields the caller omitted, keys the template never reads, and strict-render failures with the recorded error.
* A trigger fire's detail sheet (Events page) shows a **Contract check**: the raw body replayed through the same validator the live fire used, so a key the fire dropped is labeled exactly as dropped.
* Both object pages walk you through wiring with a step list whose completion is derived from real data: a key exists, your test fire arrived, an automation is live.

Together the loop is: generate types, pin them in calls, gate drift in CI, and read the live ledger when something looks off.
