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

# Authentication & security

> Brew Public API v1 authentication, permission scopes, key lifecycle, brand binding, and production security best practices.

Brew uses **API key authentication**. Each key is bound to exactly one brand at creation time, carries a permission scope set, and ships over HTTPS as either an `Authorization: Bearer` header or the convenience `X-API-Key` header.

## Quickstart

```bash theme={null}
curl -H "Authorization: Bearer brew_your_api_key" \
  https://brew.new/api/v1/domains
```

```ts theme={null}
import { createBrewClient } from '@brew.new/sdk'

const brew = createBrewClient({ apiKey: process.env.BREW_API_KEY! })
const { data: domains } = await brew.domains.list()
```

Get a key at [brew.new/settings/api](https://brew.new/settings/api).

## Headers (use ONE)

| Header          | Format          | Notes                                                                     |
| --------------- | --------------- | ------------------------------------------------------------------------- |
| `Authorization` | `Bearer brew_…` | Recommended. Plays nicely with most HTTP clients + observability tooling. |
| `X-API-Key`     | `brew_…`        | Convenience header. Same behaviour as the bearer form.                    |

Send exactly one. Sending both is allowed; sending neither returns `401 AUTHENTICATION_REQUIRED`. Malformed keys return `401 INVALID_API_KEY`; revoked keys return `401 API_KEY_REVOKED`.

## Brand binding (one brand per key)

Every API key is bound to **exactly one brand** at creation time. The binding is enforced server-side:

* The brand id is resolved from the key on every request — clients **never** send a `brandId` field. Sending one returns `400 INVALID_REQUEST` with `param: "brandId"`.
* All brand-scoped reads filter to the key's brand automatically. Cross-brand identifiers surface as `404` (not `403`) so the API never confirms the existence of resources in another brand.
* All brand-scoped writes target only the key's brand.

The lone organization-wide endpoint is `GET /v1/templates` (the public template catalog) — every other endpoint is brand-scoped.

To operate on a different brand, switch brands in the dashboard at [brew.new/settings/api](https://brew.new/settings/api) and create a new key for that brand. A brand can have any number of keys (dev, staging, production, per-service, per-teammate); each acts on the same single brand it was created against.

## Permission scopes

Each key carries one or more permission scopes. Routes require either the route's scope or `all`. Missing permission returns `403 INSUFFICIENT_PERMISSIONS` with `error.param` pointing at the missing scope name.

### Scope implication (coarse scopes satisfy granular ones)

Brew supports both **coarse** scopes (`contacts`, `emails`, `automations`) and **granular** least-privilege scopes (`audiences`, `domains`, `sends`). A coarse scope automatically satisfies the granular scopes it implies, so existing keys keep working — you only reach for the granular scopes when you want to lock a key down further.

| Granted scope                                         | Satisfies                     |
| ----------------------------------------------------- | ----------------------------- |
| `contacts`                                            | `contacts`, `audiences`       |
| `emails`                                              | `emails`, `domains`, `sends`  |
| `automations`                                         | `automations`                 |
| `all`                                                 | everything                    |
| each granular scope (`audiences`, `domains`, `sends`) | itself only (least privilege) |

The complete set of valid scope values accepted when a key is created is `contacts`, `emails`, `automations`, `audiences`, `domains`, `sends`, `transactional`, and `all`.

### Per-route required scope

| Route                                                                    | Required scope (or an implier) |
| ------------------------------------------------------------------------ | ------------------------------ |
| `/v1/contacts`, `/v1/contacts/search`, `/v1/fields`                      | `contacts`                     |
| `/v1/audiences` (all methods)                                            | `audiences` (or `contacts`)    |
| `/v1/domains` (all methods)                                              | `domains` (or `emails`)        |
| `/v1/emails`, `/v1/emails/import`, `/v1/templates`                       | `emails`                       |
| `/v1/sends` (POST, incl. `{ test: true }`), `/v1/sends/{sendId}/cancel`  | `sends` (or `emails`)          |
| `/v1/brand`, `/v1/usage`                                                 | `emails`                       |
| `/v1/analytics/campaigns`, `/v1/analytics/events`, `/v1/analytics/sends` | `emails`                       |
| `/v1/analytics/automations`, `/v1/analytics/trigger-instances`           | `automations`                  |
| `/v1/automations` (incl. `/triggers*`, `/runs*`)                         | `automations`                  |

`transactional` is a reserved scope with no route today.

**Principle of least privilege.** For a back-end that only fires triggers, issue a key with `automations` only — even if it leaks, it can't list contacts. For a service that only inspects send health, issue `sends` (it can read `/v1/analytics/sends` but not generate emails or manage domains). The dashboard surfaces each key's scope set so you can audit + rotate.

## Key lifecycle

| Action     | Where                                                                                                                                              |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Create** | Dashboard → [API Keys](https://brew.new/settings/api). Choose a brand and a scope set; the secret is shown ONCE — store it in your secret manager. |
| **List**   | Dashboard. The list shows the key id, prefix, scope, brand binding, and last-used timestamp. The full secret is never re-exposed.                  |
| **Rotate** | Dashboard. Create a fresh key with the same scope set; cut traffic over (zero-downtime); revoke the old key after a grace window.                  |
| **Revoke** | Dashboard. Revoked keys immediately return `401 API_KEY_REVOKED` on every request.                                                                 |
| **Audit**  | Dashboard surfaces the last-used timestamp per key. Pair with your platform's request logs (e.g. Vercel) for full attribution via `x-request-id`.  |

There is **no API for key CRUD** today — provisioning is human-in-the-loop through the dashboard so a compromised key can't mint more. If you need programmatic key management for a SOC2 / SAST pipeline, contact us.

## Production security checklist

| Practice                                                                                                                       | Why                                                                  |
| ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- |
| **Keys live in environment variables on the server only.** Never in client JS, mobile binaries, or git.                        | A key in the browser can be exfiltrated and used against your brand. |
| **Use short-lived deploys' env vars + a secret manager.** Vercel + Vault / Doppler / 1Password / AWS Secrets Manager all work. | Centralised rotation, audit trail.                                   |
| **One key per environment.** Separate dev / staging / prod keys.                                                               | Blast radius — revoke staging without touching prod.                 |
| **One key per service** when feasible. Webhook receiver gets a key; cron worker gets a different key.                          | Granular audit + rotation.                                           |
| **Set `Idempotency-Key`** on every retried `POST` so a replayed key burst doesn't double-fire.                                 | See [Idempotency](/api-reference/api/idempotency).                   |
| **Honor `Retry-After`** on `429`.                                                                                              | See [Rate limits](/api-reference/api/rate-limits).                   |
| **Log every API call's `x-request-id`** from the response.                                                                     | Support diagnostics; correlate end-to-end through Brew's pipeline.   |
| **Pin to the official TypeScript SDK** when possible (`@brew.new/sdk@latest`).                                                 | Auto-retries, auto-idempotency keys, typed error envelope.           |
| **Verify webhook signatures** (when outbound webhooks ship).                                                                   | See [Webhooks & events](/api-reference/api/events-and-webhooks).     |

## What we do NOT support today

For transparency:

* **OAuth 2.0 / token exchange** for end-user authorization. The Brew Public API is server-to-server today; if you want per-user OAuth on top of the API, build it in your app and hold the Brew key on your server. Contact us if you have a use case that genuinely needs OAuth.
* **PATCH idempotency.** The `Idempotency-Key` header is honored only on `POST`. `PATCH` operations are naturally idempotent at the resource level — re-sending the same body is safe.
* **Public key-management API.** Keys are minted from the dashboard.

These are roadmap items where there's customer pull; tell us at the link below if your integration needs any of them.

## Errors

| Code                       | HTTP | When                                                                                                          |
| -------------------------- | ---- | ------------------------------------------------------------------------------------------------------------- |
| `AUTHENTICATION_REQUIRED`  | 401  | No `Authorization` / `X-API-Key` header.                                                                      |
| `INVALID_API_KEY`          | 401  | The key doesn't parse (e.g. wrong prefix).                                                                    |
| `API_KEY_REVOKED`          | 401  | The key was revoked in the dashboard.                                                                         |
| `INSUFFICIENT_PERMISSIONS` | 403  | The key's scope set doesn't include the route's required scope. `error.param` carries the missing scope name. |

See [Errors](/api-reference/api/errors) for the full error envelope + every code in the catalog.

## See also

* [API introduction](/api-reference/api/api-introduction) — the overview that links every reference page.
* [Idempotency](/api-reference/api/idempotency) — set `Idempotency-Key` on every retried `POST`.
* [Rate limits](/api-reference/api/rate-limits) — per-route policies + the `429` cookbook.
* [SDK authentication](/sdks/authentication) — TypeScript SDK auth specifics.

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

<Tabs>
  <Tab title="Self-Service Tools">
    <CardGroup cols="2">
      <Card title="Search Documentation" icon="magnifying-glass" color="#c44925">
        Type in the "Ask any question" search bar at the top left to instantly find relevant documentation pages.
      </Card>

      <Card title="ChatGPT/Claude Integration" icon="robot" color="#c44925">
        Click "Open in ChatGPT" at the top right of any page to analyze documentation with ChatGPT or Claude for deeper insights.
      </Card>
    </CardGroup>
  </Tab>

  <Tab title="Talk to Our Team">
    <CardGroup cols="2">
      <Card title="Schedule a Call" icon="calendar" color="#c44925" href="https://calendar.google.com/calendar/u/0/appointments/schedules/AcZssZ1iYoRUG1J792XQpbuQLjSRRDupr7MwraFK-HQRCtTYdBmrQi8nZu2qXfzKQigb8gbKJK3KN3-R">
        Book time with our founders for personalized guidance on strategy, best practices, or complex implementation questions.
      </Card>

      <Card title="Call Us Directly" icon="phone" color="#c44925">
        Need immediate assistance? Reach us at **+1-(332)-203-2145** for urgent issues or time-sensitive questions.
      </Card>

      <Card title="Slack Channel" icon="slack" color="#c44925">
        Our preferred support channel. You'll receive an invite after signup for direct founder support and fast responses.
      </Card>

      <Card title="Email Support" icon="envelope" color="#c44925" href="mailto:support@brew.new">
        Contact us at **[support@brew.new](mailto:support@brew.new)** for detailed inquiries or if you prefer not to use Slack.
      </Card>
    </CardGroup>
  </Tab>
</Tabs>
