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

# Link from your app

> Link a Bilt member from your product with OAuth 2.0, PKCE, and an id_token from the Bilt IdP

Bilt is the OpenID Connect (OIDC) identity provider (IdP) in this flow. You run the authorization code flow with PKCE, then send the resulting `id_token` to Bilt to create the link.

## Before you begin

* Get your two OAuth clients and the environment URLs. See [Environments and credentials](/partners/environments-and-credentials.md).
* Register your redirect URIs with Bilt for staging and production.
* Run the flow from your backend, as a confidential client.
* Have a JSON Web Token (JWT) library that validates RS256 signatures.
* Read endpoints and keys from `<issuer>/.well-known/openid-configuration` instead of hard-coding them. [Environments and credentials](/partners/environments-and-credentials.md) lists each issuer.

## How it works

```mermaid theme={null}
sequenceDiagram
    participant Member
    participant You as Your backend
    participant IdP as Bilt IdP
    participant API as Bilt API
    Member->>You: Choose "Link Bilt"
    You->>Member: Redirect to IdP with state and code_challenge
    Member->>IdP: Sign in and consent
    IdP->>You: Redirect with code and state
    You->>IdP: Exchange code with code_verifier
    IdP-->>You: id_token
    You->>API: POST /v1/account-links/oauth-link
    API-->>You: 200 LINKED
```

## Steps

**1. Create the session values**

For each sign-in, generate a random `state` and a `code_verifier` of 43 to 128 URL-safe characters. Store both in a server-side session. Compute `code_challenge` as `BASE64URL(SHA256(code_verifier))`.

**2. Send the member to the IdP**

Read `authorization_endpoint` from the discovery document and redirect the member there with these parameters:

* `client_id`: your IdP client.
* `redirect_uri`: an exact match of a registered URI.
* `response_type=code`
* `scope=openid`. Add `email` or `profile` only if you need them.
* `state`
* `code_challenge` and `code_challenge_method=S256`. The `plain` method is not accepted.
* `nonce` (optional)

**3. Handle the callback**

Reject a `state` that does not match the stored value before you read anything else. The IdP does not enforce `state`.

Then check for an `error` parameter. Do not retry `access_denied`: the member changed their mind. `invalid_request`, `unauthorized_client`, and `server_error` point to a setup or IdP problem.

Otherwise, read the `code`. It is short-lived and single-use.

**4. Exchange the code**

From your backend, post the code to `token_endpoint` as form data:

```bash theme={null}
curl -X POST "$TOKEN_ENDPOINT" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -u "$IDP_CLIENT_ID:$IDP_CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code="$CODE" \
  -d redirect_uri="$REDIRECT_URI" \
  -d code_verifier="$CODE_VERIFIER"
```

Common failures are a wrong content type, a changed `redirect_uri`, and a used code. The IdP answers `invalid_grant`, `invalid_client`, `invalid_request`, or `redirect_uri_mismatch`. Start the flow again instead of retrying the code.

**5. Validate the id\_token**

You only need the `id_token` from the response. Validate it before you send it:

* The signature verifies against `jwks_uri` with `RS256`. Cache the key set, and refresh it when a key ID is unknown.
* `iss` exactly matches the discovery issuer.
* `aud` is your IdP client ID.
* `exp` is in the future. Bilt allows 30 seconds of clock skew.
* `jti` is present.

The `sub` claim is the Bilt member ID, a stable UUID. Key on `sub`, not on email.

**6. Create the link**

Send the token, unchanged and right away, to [Create a link](/api-reference/account-links/create-a-link.md). Add your `partnerUserId`: a stable, opaque ID with no personal data in it. Store the `linkId` from the response.

Bilt never sees the member access token from the exchange. You can discard it.

### Handle the id\_token

* Use each token once. The only reuse is resending an identical request whose response was lost.
* Do not rewrite or re-sign the token. It can be up to 8,192 characters.
* Do not log or store the token.
* A staging token sent to production fails with `INVALID_ID_TOKEN`.

## What can go wrong

* The member reuses a token on another account. Bilt answers [`409 ID_TOKEN_REPLAYED`](/partners/errors-and-retries#error-codes).
* The member or your account is already linked. Bilt answers [`409 ALREADY_LINKED` or `409 IDENTITY_ALREADY_LINKED`](/partners/errors-and-retries#error-codes).
* The token expired before you sent it. Bilt answers [`401 ID_TOKEN_EXPIRED`](/partners/errors-and-retries#error-codes).
* The token was minted for another client or environment. Bilt answers [`401 ID_TOKEN_AUDIENCE_MISMATCH` or `401 INVALID_ID_TOKEN`](/partners/errors-and-retries#error-codes).
* The code exchange fails with `invalid_grant`. Start the flow again. See [Create a link](/api-reference/account-links/create-a-link.md).

## Next steps

* [Errors and retries](/partners/errors-and-retries.md) covers what to do with each error code.
* [Unlink an account](/partners/account-linking/unlink-an-account.md) covers how to end the link.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.