> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.astropods.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.astropods.com/_mcp/server.

# Let your agent use a user's connections

A connection is a third-party account, such as GitHub, that a person has linked to Astropods. When you declare a connection in `astropods.yml`, each user who chats with your agent decides whether to let it use their account. The agent then acts as that user, for example by opening a pull request in their name, instead of sharing one static token across everyone.

By the end of this guide, your agent asks for a GitHub connection, the user allows it, and the agent calls GitHub as that user.

## Before you start

* An agent project you can run with `ast project start`. [Your first project](/get-started) sets one up.
* You are signed in with [`ast login`](/cli/top-level#login).
* Users link the provider on their personal account first, under **Settings** > **Connectors**. The consent card in chat also offers a **Connect** button.

## Supported providers

| Provider | `provider` value |
| -------- | ---------------- |
| GitHub   | `github`         |
| Postman  | `postman`        |
| Box      | `box`            |

Pushing a spec with any other `provider` value fails validation.

## How it works

<defs>
  <path d="M0,0 L10,5 L0,10 z" />
</defs>

<rect x="24" y="60" width="190" height="100" rx="10" />

User

Allows in chat

<rect x="316" y="60" width="190" height="100" rx="10" />

Your agent

Asks for a token

<rect x="606" y="60" width="190" height="100" rx="10" />

Provider

Acts as the user

1. Your spec lists the connections the agent needs.
2. Before the first message of a new chat, the user sees a consent card. It shows each provider, your reason, and the scopes.
3. The user allows it for a duration they pick: until they revoke it, 24 hours, 7 days, or 30 days.
4. During a turn, your agent requests an access token for that user and calls the provider with it.

Astropods never stores the provider token. The agent gets a token only for a user who allowed it and is chatting now.

## Declare connections

Add a `connections` list to `astropods.yml`:

**`astropods.yml`**

```yaml title="astropods.yml"
connections:
  - provider: github
    scopes: [repo, read:org]
    required: true
    reason: Open pull requests in your repositories
  - provider: postman
    required: false
    reason: Link collections to the pull requests it opens
```

| Field      | Required | Description                                                                                                                   |
| ---------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `provider` | Yes      | The provider to connect. Each provider appears once.                                                                          |
| `reason`   | Yes      | Shown on the consent card. 120 characters or fewer.                                                                           |
| `scopes`   | No       | Scopes your agent needs. Each is a single word with no spaces.                                                                |
| `required` | No       | When `true`, chat stays blocked until the user allows the connection. When `false`, the user can skip it. Defaults to `true`. |

Run `ast spec validate` to check the file before you push. Connections are separate from [`integrations`](/known-integrations), which are labels for the Agent Card.

## Use a connection in your agent

Create a `ConnectionClient` once, then call `getToken` with the provider and the ID of the user in the current turn. Your adapter passes that ID as `userId` in the stream options.

#### TypeScript

```ts
import { ConnectionClient, ConnectionError } from "@astropods/adapter-core/connections";

const connections = new ConnectionClient();

async function openPullRequest(userId: string) {
  try {
    const { accessToken } = await connections.getToken("github", userId);
    return await fetch("https://api.github.com/user", {
      headers: { authorization: `Bearer ${accessToken}` },
    });
  } catch (err) {
    if (err instanceof ConnectionError) {
      return `I can't use your GitHub account yet (${err.code}).`;
    }
    throw err;
  }
}
```

#### Python

```python
from astropods_adapter_core.connections import ConnectionClient, ConnectionTokenError

connections = ConnectionClient()

async def open_pull_request(user_id: str):
    try:
        token = await connections.get_token_async("github", user_id)
    except ConnectionTokenError as err:
        return f"I can't use your GitHub account yet ({err.code})."
    return token
```

Install the extra first: `pip install "astropods-adapter-core[connections]"`.

The client reads its credentials from the environment Astropods sets for your agent. It reuses a token until about a minute before the token expires.

## Handle refusals

A refused request carries a `code`:

| Code                    | Meaning                                                                        | What your agent can do                                         |
| ----------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------- |
| `not_consented`         | The user never allowed this provider, revoked it, or it expired.               | Ask the user to start a new chat and allow it.                 |
| `not_active`            | The user has not messaged the agent in web chat in the last 15 minutes.        | Retry after the user's next message.                           |
| `not_connected`         | The user disconnected the provider.                                            | Ask the user to reconnect under **Settings** > **Connectors**. |
| `needs_reauthorization` | The provider needs the user to sign in again, or a requested scope is missing. | Ask the user to reconnect.                                     |
| `unavailable`           | The request failed or the server could not be reached.                         | Retry later.                                                   |

## Test locally

Run the agent with `ast dev`. When the spec declares `connections`, the CLI opens a dev session and the agent calls the provider as you, from your own personal account. Local runs skip the consent card. Connect the provider under **Settings** > **Connectors** first.

## Manage access

Users review what they have allowed in **Settings** > **Connectors** > **Agent access**. **Revoke access** takes effect on the agent's next token request. Disconnecting a provider on a personal account revokes every agent's access to it.

As the agent's owner, you see how many people allowed each connection under **Configure** > **Access**. You never see who.

## What users see

When a user opens a new chat with an agent that declares connections they haven't allowed, a consent card appears above the message box. Here `<agent>` stands for your agent's name.

| Part of the card       | What it shows                                                                                                                                                                                           |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Title                  | "Allow `<agent>` to use your accounts"                                                                                                                                                                  |
| Subtitle               | "`<agent>` acts as you, with the permissions listed, while you chat with it."                                                                                                                           |
| One row per connection | The provider's icon and name, your `reason`, and your `scopes`. A **Required** label marks required connections.                                                                                        |
| Row action             | A checkbox when the provider is connected. Required rows stay checked. When it isn't connected, a **Connect** button, such as **Connect GitHub**, or **Reconnect** if the provider needs a new sign-in. |
| **Allow for**          | **Until I revoke** (default), **24 hours**, **7 days**, or **30 days**. It applies to every checked row.                                                                                                |
| Buttons                | **Allow access** saves the choice. **Not now** closes the card.                                                                                                                                         |

While the card is open, the message box is disabled and reads "Allow access to chat with `<agent>`".

If a required connection is still not allowed after **Not now**, the card is replaced by a notice: "`<agent>` needs access to your accounts before you can chat". Its **Review access** button reopens the card. If only optional connections are left, **Not now** enables the message box.

| Situation                        | What the user sees                                                        |
| -------------------------------- | ------------------------------------------------------------------------- |
| Provider not connected           | **Connect** on the row. After sign-in, the user returns to the same chat. |
| No personal account              | "Needs a personal account" on the row.                                    |
| Allow with nothing connected     | "Connect an account first, then allow access."                            |
| Saving fails                     | "Couldn't save your choice. Try again."                                   |
| Status can't load                | "Couldn't load account access", with **Try again**.                       |
| Every connection already allowed | No card. The chat starts as usual.                                        |

## What to expect

* The connection always comes from the user's personal account, even when the agent belongs to a team account. A connection made on a team account does not count.
* Token requests succeed only in Astropods web chat. Agents that serve their own UI and Slack chats get `not_active`.
* When a new version adds a scope the user has not seen, the consent card appears again.
* Background work with no open chat can't fetch a token.

## Next steps

* [Managing secrets](/secrets): store a shared, account-wide credential instead
* [Known integrations](/known-integrations): label what your agent talks to