`add-integration` wires up a new external service, Slack, OpenAI, S3, any third-party API, using
the adapter+fake seam every existing integration in the kit already follows. The concept-level
explanation lives at
[The Adapter+Fake Pattern](/docs/building-features/the-adapter-fake-pattern); this page is the
skill's own steps.

## The rule this skill enforces

Never call a third-party API directly from a controller or a model. Everything routes through
`app/adapters/`, copying either `app/adapters/billing.rb` (with its `billing/local.rb` and
`billing/stripe.rb`) or the simpler `app/adapters/bot_check.rb` as the template.

## The steps

1. Create `app/adapters/<service>.rb` as a module with a `.for(...)`, `.client`, or `.enabled?`
   entry point that returns the real client when its credential environment variable is present,
   and a deterministic fake with canned, sensible values otherwise. Credentials resolve `ENV` first,
   then Rails encrypted credentials.
2. Put the real client and the fake in `app/adapters/<service>/`. The fake has to be complete enough
   that the whole feature works offline against it, not just enough to avoid raising an error.
3. Honor a force-fake override for demos and CI, following the pattern in `Billing#stripe?`.
4. Rescue and report failures through `ErrorReporter.report(e, context: {...})`, and decide
   deliberately whether this integration should fail open or fail closed; `BotCheck#verify` is the
   reference for failing closed.
5. Document the new credential in [Bring Your Own Keys](/docs/bring-your-own-keys/optional-integrations)
   and in `.env.example`.
6. If the integration sends the user off-site (a hosted checkout, an OAuth consent screen, a hosted
   onboarding page), add its host to `policy.form_action` in
   `config/initializers/content_security_policy.rb` and pin it in
   `spec/requests/content_security_policy_spec.rb`. This step is the one most often missed, and the
   failure is completely silent: no exception, no flash, nothing in the network tab, just a button
   that appears dead. Add `data-turbo="false"` to any form whose action redirects off-site, on the
   form or the submit button, or Turbo will submit it via `fetch` and have the cross-origin redirect
   blocked by `connect-src` instead.
7. Write a spec covering both the fake path (works with zero credentials) and the
   enabled-but-invalid path (credentials present, service rejects them).
8. Run `bin/check`.

## Next

See the equivalent walkthroughs for email and background jobs:
[The add-mailer and add-job Skills](/docs/claude-code/skill-add-mailer-and-add-job).
