# `PhoenixKit.Integrations`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.102/lib/phoenix_kit/integrations/integrations.ex#L1)

Centralized management of external service integrations.

Stores credentials (OAuth tokens, API keys, bot tokens, etc.) using the
existing `PhoenixKit.Settings` system with `value_json` JSONB storage.
Each integration is a JSON blob under a key like `"integration:google"`.

## Auth types supported

- `:oauth2` — Google, Microsoft, Slack, etc. (client_id/secret + access/refresh tokens)
- `:api_key` — OpenRouter, Stripe, SendGrid, etc. (single API key)
- `:key_secret` — AWS, Twilio, etc. (access key + secret key)
- `:bot_token` — Telegram, Discord, etc. (single bot token)
- `:credentials` — SMTP, databases, etc. (freeform credential map)

## Usage

    # Check if a provider is connected
    PhoenixKit.Integrations.connected?("google")

    # Get credentials for API calls
    {:ok, creds} = PhoenixKit.Integrations.get_credentials("google")
    # => %{"access_token" => "ya29...", "token_type" => "Bearer", ...}

    # Make an authenticated request with auto-refresh on 401
    {:ok, response} = PhoenixKit.Integrations.authenticated_request("google", :get, url)

# `add_connection`

```elixir
@spec add_connection(String.t(), String.t(), String.t() | nil) ::
  {:ok, map()} | {:error, term()}
```

Adds a new named connection for a provider.

The name can be any string alphanumeric with hyphens (e.g., "company-drive").

# `authenticated_request`

```elixir
@spec authenticated_request(String.t(), atom(), String.t(), keyword()) ::
  {:ok, Req.Response.t()} | {:error, term()}
```

Make an authenticated HTTP request with automatic token refresh on 401.

For OAuth providers: adds Bearer token, retries with refreshed token on 401.
For API key providers: adds Bearer token from the api_key.
For bot token providers: returns credentials for the caller to use directly.

`opts` are passed through to `Req.request/1`.

# `authorization_url`

```elixir
@spec authorization_url(String.t(), String.t(), String.t() | nil, String.t() | nil) ::
  {:ok, String.t()} | {:error, term()}
```

Build the OAuth authorization URL for a provider.

Accepts an optional `state` parameter for CSRF protection. Use
`PhoenixKit.Integrations.OAuth.generate_state/0` to generate one,
store it in the session or socket assigns, and verify it when the
callback arrives.

# `connected?`

```elixir
@spec connected?(String.t()) :: boolean()
```

Check if an integration is connected and has valid credentials.

# `disconnect`

```elixir
@spec disconnect(String.t(), String.t() | nil) :: :ok
```

Disconnect an integration (remove tokens, keep setup credentials).

For OAuth: removes access_token, refresh_token, keeps client_id/client_secret.
For API key/bot token: removes the key entirely.

# `exchange_code`

```elixir
@spec exchange_code(String.t(), String.t(), String.t(), String.t() | nil) ::
  {:ok, map()} | {:error, term()}
```

Exchange an OAuth authorization code for tokens and save them.

# `get_credentials`

```elixir
@spec get_credentials(String.t()) ::
  {:ok, map()} | {:error, :not_configured | :deleted}
```

Get credentials for a provider, suitable for making API calls.

Returns the full integration data map. The caller extracts what it needs
based on the auth type (e.g., `"access_token"` for OAuth, `"api_key"` for API key).

# `get_integration`

```elixir
@spec get_integration(String.t()) ::
  {:ok, map()} | {:error, :not_configured | :invalid_provider_key}
```

Get the full integration data for a provider.

Returns the entire JSON blob including credentials, status, and metadata.
Automatically migrates legacy settings keys (e.g., `"document_creator_google_oauth"`)
on first access.

# `list_connections`

```elixir
@spec list_connections(String.t()) :: [
  %{uuid: String.t(), name: String.t(), data: map()}
]
```

Lists all connections for a provider.

Returns a list of `%{uuid: uuid, name: name, data: data}` maps, with "default" first.
The `uuid` is the stable identifier for the settings row.

# `list_integrations`

```elixir
@spec list_integrations() :: [map()]
```

List all configured integrations (those that have saved data).

# `list_providers`

```elixir
@spec list_providers() :: [map()]
```

List all known providers.

# `load_all_connections`

```elixir
@spec load_all_connections([String.t()]) :: %{
  required(String.t()) =&gt; [%{uuid: String.t(), name: String.t(), data: map()}]
}
```

Loads all connections for multiple providers in a single database query.

More efficient than calling `list_connections/1` in a loop.
Returns a map of `provider_key => [%{uuid, name, data}]`.

# `record_validation`

```elixir
@spec record_validation(String.t(), :ok | {:error, term()}) :: :ok
```

Persist the outcome of a connection check (manual or automatic) onto the
integration record and broadcast a PubSub event.

Writes `status`, `validation_status` and `last_validated_at`. Safe to call
from automatic code paths (e.g. failed token refresh) — it only touches the
DB / broadcasts when something actually changed, avoiding write churn on the
happy path.

# `refresh_access_token`

```elixir
@spec refresh_access_token(String.t()) :: {:ok, String.t()} | {:error, term()}
```

Refresh an expired OAuth access token and save the new one.

On failure, stamps the integration record with `status: "error"` and a
human-readable `validation_status` so the UI reflects the broken state
without waiting for an admin to click "Test Connection".
On success following a previously-errored state, auto-recovers the status
back to `"connected"`.

# `remove_connection`

```elixir
@spec remove_connection(String.t(), String.t(), String.t() | nil) ::
  :ok | {:error, term()}
```

Removes a named connection. The "default" connection cannot be removed.

# `run_legacy_migrations`

```elixir
@spec run_legacy_migrations() :: :ok
```

Run one-time legacy migrations for all known providers.

Call this at application boot (e.g., in `Application.start/2`) to migrate
legacy settings keys to the new `integration:{provider}:{name}` format.
Safe to call multiple times — skips providers that already have data.

# `save_setup`

```elixir
@spec save_setup(String.t(), map(), String.t() | nil) ::
  {:ok, map()} | {:error, term()}
```

Save setup credentials for a provider.

For OAuth providers, this saves client_id/client_secret.
For API key providers, this saves the api_key.
For bot token providers, this saves the bot_token.

Merges with existing data to preserve any previously obtained tokens.
Sets status to "disconnected" if no runtime credentials exist yet.

# `settings_key`

```elixir
@spec settings_key(String.t()) :: String.t()
```

Returns the settings key for a provider connection.

Accepts `"google"` (returns default connection key) or
`"google:personal"` (returns named connection key).

## Examples

    iex> PhoenixKit.Integrations.settings_key("google")
    "integration:google:default"

    iex> PhoenixKit.Integrations.settings_key("google:personal")
    "integration:google:personal"

# `validate_connection`

```elixir
@spec validate_connection(String.t(), String.t() | nil) :: :ok | {:error, String.t()}
```

Validate that a provider's credentials are working.

For OAuth: calls the provider's userinfo endpoint.
For API key / bot token: calls the provider's validation endpoint if defined.
Returns `:ok` or `{:error, reason}`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
