payloadSchema, declared when the trigger is created (integration triggers declare theirs per provider event). Every fire of POST /v1/automations/triggers/{triggerEventId}/fire is validated against it. You can also store a richer contract per trigger, versioned and renderable as types, which the /contract endpoints below cover. This guide turns those contracts into TypeScript types in your codebase, keeps them from drifting, and shows where Brew checks real fires against them.
Nested payload values (objects and arrays) are accepted everywhere. Address a nested leaf by its dotted path:
{{ user.plan }}, not {{ payload.user.plan }}. See Merge tags and variables.Generate Types with brew-cli
brew-cli types writes one file with a type per trigger contract (the command needs a key with the automations scope):
Payload. Renaming a trigger renames the type, which is exactly the drift --check exists to catch; colliding names get the trigger id appended so the file always compiles.
Field optionality follows the schema: a trigger field is optional when required: false. The email field is always required. It is the contact key downstream automations route on.
In CI, gate drift with:
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 shows up as a type change in code review instead of a runtime surprise.
Store a Contract
Brew derives a contract on every read, so the guide above works with no setup. You can also store one per trigger. A stored contract is content-hash versioned and served fromGET /v1/automations/triggers/{triggerEventId}/contract.
The response says which kind you got. source: "stored" carries a
contractHash, a version, and an enforcement mode.
source: "derived_from_schema" is the fallback, built from the trigger’s flat
payloadSchema. It can carry type: "unknown" where there was no type
evidence to work from.
Fields form a tree. Scalars are string, int, float, boolean, date, or
enum. An object node carries children. An array node carries either
children for an element object shape, or itemType for scalar elements.
Pass ?format= to get a generated artifact instead of the JSON contract:
ts, zod, jsonschema, and skill each return { format, content }.
Declare or replace the stored contract with PUT on the same URL:
- The whole tree validates before anything is written. Unknown-typed nodes
are refused, so declare a concrete type. Keys must be unique per level, and
an
objectfield must declare at least one child. - A trigger contract keeps its
emailfield. It needs a top-level{ "key": "email", "type": "string", "required": true }so automations can resolve a recipient. fieldsis optional. Changing only the enforcement mode is aPUTwith justenforcement, so you never re-send the tree to flip a switch.- The version tracks behavior, not prose. Keys, types,
required, and fallbacks bump it. Editing adescriptionor an example does not.
off until you turn it on, and the two enforcing modes are prune and
strict.
They differ only in how they treat keys the contract does not declare. prune
drops them, strict fails the payload. Declared-field rules are identical in
both.
Draft a Contract from an Example
Writing a field tree by hand is tedious when you already have the payload. Post a real example toPOST /v1/payload-contracts/infer and Brew types it for you:
date, and
object and array shapes are walked recursively. Every inferred field comes back
required: true, so relax the optional ones yourself.
Inference degrades honestly. A null or an empty array carries no type
evidence, so those land in issues with the offending path instead of a guess.
Nothing is saved either way: review the draft, then PUT it to the trigger’s
/contract.
Dry-Run a Payload
POST /v1/automations/triggers/{triggerEventId}/contract/validate runs a
payload through the same validator the live path uses, against the stored
contract when one exists. Nothing fires, sends, or writes.
valid, the per-field errors and warnings, resolvedPayload with
fallbacks applied, and prunedKeys for anything the contract does not declare.
The optional enforcement field previews a mode other than the stored one.
That is how you find out what strict would reject before you arm it.
Pin the Types in SDK Calls
fire accepts a payload type parameter, so call sites compile against the contract:
Fetch the Wiring Brief for an Agent
The trigger read accepts?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.
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:
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:- 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.
- The trigger’s page walks 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.