> ## Documentation Index
> Fetch the complete documentation index at: https://docs.capsule.new/llms.txt
> Use this file to discover all available pages before exploring further.

# Channels, Integrations, And Secrets

> Channels, user integrations, and secret handling.

These three concepts are easy to mix up at first:

* channels decide how messages enter the app
* integrations are end-user connections the app can ask for at runtime
* secrets are app-owned credentials

## Worked example

Here is what they look like when combined in one app:

```python theme={null}
app = cpsl.App(
    name="support-ops",
    image=cpsl.Image(python_packages=["openai"]),
    channels=[cpsl.Channel("support-telegram")],
    secrets=["OPENAI_API_KEY"],
)

app.add_integration(
    cpsl.Gmail(
        client_id=cpsl.Secret.from_name("GOOGLE_OAUTH_CLIENT_ID"),
        client_secret=cpsl.Secret.from_name("GOOGLE_OAUTH_CLIENT_SECRET"),
        scopes=["https://www.googleapis.com/auth/gmail.readonly"],
    )
)
```

That app can:

* answer in built-in chat and Telegram because of the channel binding
* read the operator's model key because of the secret injection
* ask each end user to connect Gmail when the product needs mailbox access

One named Telegram channel maps to one Telegram bot token and one bound app deployment. Many Telegram users can message that bot, and each Telegram user gets an isolated channel identity. Do not reuse the same BotFather token across multiple Capsule channel resources unless you are intentionally moving the webhook; Telegram only supports one webhook URL per bot token.

## Channels

### `Channel` / `ChannelRef`

`cpsl.Channel("name")` references a named channel resource created through the CLI or dashboard.

```python theme={null}
channels=[cpsl.Channel("my-telegram-bot")]
```

This is the preferred way to attach Telegram, Slack, or WhatsApp to an app.

External channel identities are not automatically the same as web sign-in identities. For example, an unlinked Telegram sender is scoped as a Telegram platform user. If your app needs web and Telegram to share user-scoped collections, create an explicit link flow in your app or use an app/owner-scoped store intentionally.

### `Chat`

`cpsl.Chat()` represents the built-in web chat. It exists mainly for backward compatibility because web chat is enabled implicitly.

### `API`

`cpsl.API()` represents synchronous request/response access.

### Deprecated inline channel classes

These still exist but are deprecated:

* `cpsl.Telegram(...)`
* `cpsl.Slack(...)`
* `cpsl.WhatsApp(...)`

Prefer named channel resources plus `cpsl.Channel("name")`.

## Integrations

Declare integrations with `app.add_integration(...)`.

### OAuth integrations

```python theme={null}
app.add_integration(
    cpsl.GitHub(
        client_id=cpsl.Secret.from_name("GITHUB_CLIENT_ID"),
        client_secret=cpsl.Secret.from_name("GITHUB_CLIENT_SECRET"),
        scopes=["repo"],
    )
)
```

### Secret-based integrations

```python theme={null}
app.add_integration(cpsl.AWS())
```

Known secret integrations include:

* `aws`
* `tailscale`

You can also declare a custom secret-form integration by passing `fields=[...]`.

Reading an integration at runtime usually looks like this:

```python theme={null}
@app.data("threads", access="authenticated")
async def get_threads(ctx: cpsl.RequestContext):
    gmail = ctx.integrations.get(cpsl.Integration.GMAIL)
    if not gmail:
        return {"threads": [], "gmail_connected": False}

    return {"threads": await threads.find(limit=50), "gmail_connected": True}
```

## `IntegrationCredentials`

At runtime, connected credentials appear on `session.integrations`.

### Fields

| Field          | Meaning                                                  |
| -------------- | -------------------------------------------------------- |
| `access_token` | OAuth bearer token or empty for secret-form integrations |
| `token_type`   | Usually `"Bearer"`                                       |
| `scopes`       | Granted scopes                                           |
| `expires_at`   | Expiry timestamp when available                          |
| `fields`       | Submitted secret-form fields such as AWS keys            |

## Secret API

### `Secret.from_name(name)`

Creates a secret reference.

```python theme={null}
secret = cpsl.Secret.from_name("OPENAI_API_KEY")
```

### `secret.value`

Resolves the runtime value:

```python theme={null}
api_key = cpsl.Secret.from_name("OPENAI_API_KEY").value
```

Capsule checks the environment first, then uses the runtime resolver if necessary.

That means this works both when the secret was injected directly into the runtime and when Capsule needs to fetch it through the runtime connection.

Use secret references for:

* app-owned API keys
* OAuth client credentials
* channel credentials

## Prompting helpers

These are the most important runtime entry points:

* `await session.prompt_integration(type, reason="", timeout=...)`
* `await session.require_integration(type, reason="", timeout=...)`
* `await session.show_integration(type, reason="")`

Use the first when the current flow cannot continue without the credential. Use the second when the connection is optional.

Example of the non-blocking variant:

```python theme={null}
await session.show_integration(
    cpsl.Integration.GITHUB,
    reason="Connect GitHub to unlock project analysis",
)
```

## See also

* [Connect Services](/build/connect-services)
* [Session And Request Context Reference](/reference/session-and-request-context)
* [CLI Reference](/reference/cli)
