# Set up OAuth for Link CLI

Connect your agent to your customer's Link account by registering an OAuth client and implementing the authorization flow.

Before your agent can retrieve payment credentials or financial data, it needs an access token from a customer. Hosted agents use the authorization code flow described here, which uses a registered OAuth client.

To authorize a local agent running on your own machine, use the device authorization flow instead. See the [link-cli repository](https://github.com/stripe/link-cli).

## Before you begin

Before you begin, make sure that:

- You have a [Stripe account](https://dashboard.stripe.com/register).
- Your agent is a hosted service that you operate on behalf of your customers.
- Your customers are US consumers. Your own business can be outside the US.
- You’re familiar with [OAuth 2.0 authorization code flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1) concepts.

Both authorization flows authenticate against `login.link.com` and produce an access token that Link CLI reads from `LINK_ACCESS_TOKEN`. The difference is who holds the client credentials. With device authorization, Link CLI uses a built-in public client, so there’s nothing for you to register.

## Register a confidential OAuth client

A hosted agent requires a confidential OAuth client to securely authenticate and manage customer authorization. Unlike public clients, a confidential client stores credentials server-side—appropriate when you control the backend.

To register:

1. [Contact Stripe](https://stripe.com/contact/sales) to request an OAuth client from Link.
2. Complete the registration form that Stripe sends you.
3. Stripe provides your `client_id` and `client_secret`.
4. Store both values securely on your server.

> #### Protect your client credentials
> 
> Never embed `client_secret` in client-side code, agent skill files, or version control. Store it in a secrets manager accessible only to your backend.

## Construct the authorization URL

Redirect the customer to the Link authorization endpoint to request access to their wallet.

```url
https://login.link.com/auth?key=pk_live_YOUR_PUBLISHABLE_KEY&client_id=YOUR_CLIENT_ID&redirect_uri=https://yourapp.com/callback&response_type=code&scope=payment_methods.agentic%20userinfo:read&state=RANDOM_STATE_VALUE&code_challenge=YOUR_PKCE_CODE_CHALLENGE&code_challenge_method=S256
```

URL-encode each parameter value. The `scope` parameter takes a space-separated list, so encode the separators as `%20`.

### Parameters

The authorization URL has the following parameters:

| Parameter | Required | Description |
| --- | --- | --- |
| `key` | Yes | Your Stripe publishable key. |
| `client_id` | Yes | Your OAuth client ID. |
| `redirect_uri` | Yes | The URI where Link redirects the customer after authorization. Must match the URI registered with your OAuth client. |
| `response_type` | Yes | Must be `code`. |
| `scope` | Yes | Space-separated list of OAuth scopes. See [Available scopes](https://docs.stripe.com/agentic-commerce/link-cli/oauth.md#available-scopes). Scope delimiters aren’t uniform—`payment_methods.agentic` uses a period, and `userinfo:read` uses a colon. |
| `state` | Yes | A random string to prevent CSRF attacks. Verify this value when the customer returns to your redirect URI. |
| `code_challenge` | Yes | A PKCE code challenge. Generate a random `code_verifier` (43–128 characters, URL-safe), then compute `BASE64URL(SHA256(code_verifier))`. |
| `code_challenge_method` | Yes | Must be `S256`. |
| `authorization_details` | Conditional | Required for [financial insights](https://docs.stripe.com/financial-connections/agents/financial-insights.md). A JSON array specifying the data access your agent needs. See [Add financial insights to your agent](https://docs.stripe.com/financial-connections/agents/financial-insights.md) for the required actions. |

### Available scopes 

Request only the scopes your agent uses. The set of available scopes grows over time.

| Scope | Grants access to |
| --- | --- |
| `payment_methods.agentic` | Creating spend requests to retrieve one-time-use payment credentials. |
| `userinfo:read` | Basic customer information, which you can use to prefill checkout and to check whether a customer is eligible before starting a purchase. |

Financial insights uses the `authorization_details` parameter rather than a scope. See [Add financial insights to your agent](https://docs.stripe.com/financial-connections/agents/financial-insights.md).

After the customer authenticates and approves, Link redirects to:

```text
https://yourapp.com/callback?code=AUTHORIZATION_CODE&state=YOUR_STATE_VALUE
```

Your backend must:

1. Verify that `state` matches the value you sent.
2. Extract the `code` parameter.
3. Exchange the code for tokens within 10 minutes (codes are single-use).

## Exchange the code for tokens

To exchange the code for tokens, make a request to the authorization endpoint:

```bash
curl -X POST https://login.link.com/auth/token \
  -d "grant_type=authorization_code" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "redirect_uri=https://yourapp.com/callback" \
  -d "code=AUTHORIZATION_CODE" \
  -d "code_verifier=YOUR_PKCE_CODE_VERIFIER"
```

```json
{
  "access_token": "liwltoken_...",
  "refresh_token": "liwlrefresh_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "payment_methods.agentic userinfo:read"
}
```

Store both tokens securely on your server.

### Token lifecycle

The token response includes the following tokens:

| Token type | Lifetime | Behavior |
| --- | --- | --- |
| Access token | 1 hour | Refresh before expiry using the refresh token. |
| Refresh token | 1 year | Rotated on each use. Store the new refresh token from each response. |

## Refresh an access token

To refresh an access token, make a request to the authorization endpoint:

```bash
curl -X POST https://login.link.com/auth/token \
  -d "grant_type=refresh_token" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "refresh_token=CURRENT_REFRESH_TOKEN"
```

The response includes a new `access_token` and a rotated `refresh_token`. Store the new refresh token, because the previous refresh token is invalidated.

## Configure Link CLI with your token

Set the access token as an environment variable so Link CLI can authenticate requests:

```bash
export LINK_ACCESS_TOKEN=liwltoken_...
```

Verify the connection:

```bash
link-cli user-info retrieve
```

For production, inject `LINK_ACCESS_TOKEN` from your secrets manager at runtime.

## Revoke access

To revoke a customer’s access when they disconnect from your agent:

```bash
curl -X POST https://login.link.com/auth/revoke \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "token=TOKEN_TO_REVOKE" \
  -d "token_type_hint=refresh_token"
```

## Security requirements

To secure your OAuth implementation:

- Always use PKCE (`code_challenge_method=S256`) in the authorization request.
- Always validate the `state` parameter on the OAuth callback.
- Store `client_secret` server-side only.
- Store refresh tokens securely server-side.
- Refresh access tokens before expiry instead of waiting for `401` responses.
- Exchange authorization codes within 10 minutes (they’re single-use).

## See also

- [Accept agent payments](https://docs.stripe.com/agentic-commerce/link-cli/use-link-wallet-pay-online.md)
- [Add financial insights to your agent](https://docs.stripe.com/financial-connections/agents/financial-insights.md)
