# Connect a service that signs in with OAuth

Use your own developer app at the service. Add exactly `https://app.bakedbrie.com/settings/destinations/oauth/callback` as its redirect URL. Give the client secret through `prepare_secret`; never put it in chat, a definition, a URL or a test fixture.

Want Claude Code or Codex to guide the whole setup? Paste the request in [Ask an agent to connect a service](/docs/recipes/agent-service-setup), replacing the service, board and goal. The agent can set up the connection and explain the service's developer-console steps. You complete OAuth sign-in, account choice and limits in the web app.

1. Call `manage_connection` with `action: "presets"`. Pick a preset and the operations this board needs. The answer shows the exact scopes. For another service, use a definition with `auth.type: "oauth2"` and declare only its allowed operations.
2. Create the connection with `preset`, `client_id`, `operations` and the secret drop. Default operations are reads. Writes are never selected by default.
3. Call `connect_oauth` and give its BakedBrie consent link to the connection custodian. Only a person can finish signing in. If the service lists several accounts, the person chooses one in the web app. `oauth_status` shows the grant state without a token.
4. Attach the connection to a board with `manage_board_connection`. Only a person can set or raise its monthly ceiling and the board's per-call, per-card, monthly call and monthly money limits. Calls priced at $0 still count against call limits.
5. Ask from a card, a column step, or a schedule. A paid or write call is sent at most once. For an unknown result, check the service's own records before a person chooses **Send again** in the web app.

Account values identify the actual company. QuickBooks captures `realmId` from the callback and verifies it through CompanyInfo. Xero looks up organizations and the person chooses one. Typed values such as a store name or subdomain must match their declared pattern before the one-use, ten-minute prepare link is made. Changing one requires signing in again. A rendered host must match the definition's `allowed_hosts` pin. Generic connection calls still refuse known email-send and person-message endpoints. A message uses a pinned sending adapter only after a board manager turns on [automatic sending](/docs/send-mode) for that automation; review-mode messages keep their existing approval or draft path.

Each read keeps only its declared answer fields. Data from a service appears as untrusted facts, never as agent instructions. Customer names, invoice notes and free text can be malicious. A write requires an explicitly chosen operation, the scopes it needs and the limits the person set.

## Examples, not presets

**Shopify:** type the store label before sign-in as `typed_values: {"shop":"example-store"}`. The definition declares `{from:"typed", key:"shop", pattern:"slug", max_length:63}` and pins `allowed_hosts: ["*.myshopify.com"]`. Its URLs fill only `https://{{account.shop}}.myshopify.com`; the full rendered host is checked before a token is sent. Shopify also signs its callback with an `hmac` parameter. BakedBrie does **not** check that Shopify HMAC; it uses its own state nonce, start binding and pinned host. Source: [Shopify authorization code grant](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens/authorization-code-grant), read 2026-09-29.

**Zendesk:** type `typed_values: {"subdomain":"example"}`. Declare `{from:"typed", key:"subdomain", pattern:"slug", max_length:63}` and `allowed_hosts: ["*.zendesk.com"]`; every OAuth and API URL fills only `https://{{account.subdomain}}.zendesk.com`. The full rendered host is checked before a token is sent. Source: [Zendesk OAuth clients](https://support.zendesk.com/hc/en-us/articles/4408889192858-Using-OAuth-authentication-with-your-application), read 2026-09-29.

`my.shop`, `shop@evil` and similar values fail `OAUTH_TYPED_VALUE_INVALID`; any rendered host outside the pin fails `OAUTH_TYPED_HOST_NOT_ALLOWED`.
