---
id: platform/oauth-integrations
title: User-authorized OAuth integrations
description: Register a public OAuth client, run the downloadable PKCE sample, and call the Bex API as a consented user.
keywords: [bex, oauth, pkce, api, integration]
sidebar_position: 55
last_updated: 2026-09-23
---

# User-authorized OAuth integrations

Some Bex APIs accept a **user-consented** OAuth access token in addition to a
dashboard session. That is different from a workspace **machine API key**
exchanged with `client_credentials` — see [REST & GraphQL API](./api.md).

This guide uses the downloadable sample at
[`/examples/oauth-user-client/`](/examples/oauth-user-client/README.md)
(Node 22+, no npm dependencies). Source pin: Bex `0eb2036e2`.

## 1. Obtain a registered public client

You need a **public** client (`token_endpoint_auth_method: none`) whose redirect
URI exactly matches the sample callback (default
`http://127.0.0.1:8765/callback`).

Hosted discovery may **not** advertise `registration_endpoint`. When it does
not, an installation operator must register the client with supported tooling
and give you the `client_id`. Do not invent a DCR URL or reuse a first-party /
desktop / CLI client ID.

Confirm issuer metadata at
`https://oauth.bex.co/.well-known/oauth-authorization-server` (or your
installation’s issuer). The protected-resource document at
`https://api.bex.co/.well-known/oauth-protected-resource` lists the API audience
(`https://api.bex.co/mcp` on hosted Bex).

## 2. Download and configure the sample

```bash
curl -fsSLO https://bex.co/examples/oauth-user-client-latest.zip
curl -fsSLO https://bex.co/examples/oauth-user-client-latest.zip.sha256
shasum -a 256 -c oauth-user-client-latest.zip.sha256
unzip oauth-user-client-latest.zip
cd oauth-user-client
cp .env.example .env
```
Set at least `BEX_OAUTH_CLIENT_ID`. Keep `BEX_OAUTH_SCOPES` least-privileged
(default includes `bex.read`). Scopes do not elevate workspace role.

## 3. Authorize and read

```bash
set -a && source .env && set +a
node client.mjs
```

Open the printed URL, sign in, and approve the client. The sample:

1. Listens on the registered loopback callback
2. Verifies `state` and exchanges the code with PKCE S256
3. Calls `GET /v1/services` with the access token
4. Refreshes and revokes tokens when a refresh token is present

Tokens are never printed. Denied consent or a bad `state` never triggers a
token exchange.

## 4. Offline check

Without Bex credentials:

```bash
BEX_OAUTH_FIXTURE=1 node client.mjs
```

## Troubleshooting

| Symptom | Check |
| --- | --- |
| Consent rejects PKCE | Use `code_challenge_method=S256` only |
| `invalid_grant` on exchange | Redirect URI must match registration exactly |
| 401 on `/v1/services` | Need `bex.read` (or write/sensitive) and a visible workspace |
| Admin APIs still 403 | User must be a workspace administrator; scopes ≠ role |
| No `registration_endpoint` | Ask the operator for a registered public `client_id` |

MCP clients that manage their own browser login are covered in
[Connect an Agent](./connect-an-agent.md) and [MCP server](../mcp-server.md).
Webhook administration still requires an administrator user token —
[Outbound webhooks](./webhooks.md).
