Where It Applies
Single-row upserts use
POST /v1/contacts with a different body shape and the higher-rate contacts.write_single policy (100 / min). Single-row deletes use DELETE /v1/contacts/{email}. Choose the shape that matches your workload: 100/min × 1 row = 100 contacts/min; 10/min × 1000 rows = 10,000 contacts/min.
Partial-Success Contract
When any rows fail, including when every row fails, the batch upsert returns207 instead of 200. Failures are surfaced in errors[] so callers can fix and re-submit the rejected rows.
For a completed JSON batch upsert response,
inserted + updated + failed
equals the number of input rows. Two duplicate rows contribute failed: 2.
Use summary.failed to count rejected rows.
POST /v1/contacts: Batch Upsert
Up to 1000 rows per request. email is the merge key; missing or malformed emails land in errors[]. Custom fields auto-create field definitions on the brand on first sight. See Pagination for reading them back.
The addresses below are synthetic fixtures. Replace them with test contacts you control.
plan field definition.
The response is 207 because bad@ fails validation:
TypeScript SDK
POST /v1/contacts/batch-delete: Batch Delete
Up to 1000 emails per request. The endpoint is idempotent: deleting a non-existent email surfaces in notFound[], not as an error.
missing@brewmail.dev does not, the response is 200:
Common Per-Row Error Codes
Email matching trims surrounding whitespace and ignores case. Deduplicate those
normalized addresses before retrying.
To unsubscribe a contact, submit one unambiguous row with the address and
top-level
subscribed: false to POST /v1/contacts. A rejected duplicate group
applies no subscription changes.
Foundational codes from Errors (for example RATE_LIMITED, INVALID_REQUEST) only ever apply to the whole request, never to individual rows.
Auto-Created Field Definitions
When a batch upsert references a custom field the brand doesn’t have yet, Brew creates the field definition before the write. The new column shows up immediately in:- the contacts page (filterable / sortable / searchable),
- the audience builder,
GET /v1/fields,POST /v1/contacts/searchwith{ "logic": "and", "filters": [{ "field": "plan", "operator": "equals", "value": "pro" }] }.
warnings[] as FIELD_NAME_NORMALIZED.
Partial Success Design
Batch writes report aggregate counts and failed rows so invalid data does not block valid contacts. The contract and response shape are above; see API design notes for the rationale. If your workflow requires all-or-nothing, validate and stage the input before calling the endpoint. Do not infer which successful rows were newly inserted from aggregate counts and delete them, because a batch can also update existing contacts.Related
- Idempotency: set
Idempotency-Keyon the batch so a retry doesn’t replay 1000 rows. - Rate limits:
contacts.write_batchis 10/min (vs single at 100/min); plan accordingly. - Pagination: for reading back the imported rows.
- Errors: full error catalog for the foundational request-level codes.
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.
Ask ChatGPT or Claude
Click here to open the Copy page menu at the top of this page, then send the page to ChatGPT, Claude, or your editor.