<!-- source: https://modelux.ai/docs/api/oauth -->

> OAuth 2.1 endpoints for MCP client authorization.

# OAuth reference

modelux runs an OAuth 2.1 authorization server that issues tokens to MCP
clients (Claude Desktop, Claude Code, ChatGPT, Cursor, and anything that
speaks the [MCP authorization spec](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization)).
You do not need to call these endpoints by hand — your MCP client discovers
and drives them automatically after you paste `https://api.modelux.ai/api/mcp`
as the server URL. This page documents the surface for client developers and
for anyone debugging a connection.

For the end-user setup walkthrough, see [Connecting your AI (MCP)](/docs/guides/mcp-setup).

## Metadata

### `GET /.well-known/oauth-protected-resource`

[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728). Tells clients
which authorization server mints tokens for `/api/mcp`.

```json
{
  "resource": "https://api.modelux.ai/api/mcp",
  "authorization_servers": ["https://api.modelux.ai"],
  "scopes_supported": ["mcp:read", "mcp:write"],
  "bearer_methods_supported": ["header"]
}
```

### `GET /.well-known/oauth-authorization-server`

[RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414). Advertises the
authorization server's endpoints and capabilities.

Highlights: `code_challenge_methods_supported: ["S256"]` (PKCE is required,
`plain` is rejected), `token_endpoint_auth_methods_supported: ["none"]` (all
clients are public), `grant_types_supported: ["authorization_code", "refresh_token"]`.

## Endpoints

### `POST /api/oauth/register` — Dynamic Client Registration

[RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591). Register a new
public client. JSON body; returns a `client_id` you use on subsequent
authorize / token calls. Clients are public (no `client_secret`) and must
use PKCE.

```bash
curl -sS https://api.modelux.ai/api/oauth/register \
  -H 'content-type: application/json' \
  -d '{
    "client_name": "My MCP Client",
    "redirect_uris": ["http://127.0.0.1:8787/callback"]
  }'
```

Redirect URIs must be HTTPS, except `http://localhost` and `http://127.0.0.1`
for loopback clients. Fragments are rejected.

### `GET /api/oauth/authorize` — Authorization

User-facing browser endpoint. Your MCP client opens this URL with:

| Param | Required | Notes |
|---|---|---|
| `client_id` | yes | from `/register` (or a pre-registered slug) |
| `redirect_uri` | yes | must exact-match a URI registered for the client |
| `response_type` | yes | must be `code` |
| `code_challenge` | yes | PKCE [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636) |
| `code_challenge_method` | yes | must be `S256` |
| `scope` | yes | space-separated subset of `mcp:read mcp:write` |
| `state` | recommended | echoed back on redirect |
| `resource` | yes | must be `https://api.modelux.ai/api/mcp` ([RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707)) |

Unauthenticated users are bounced to `/login` and returned here after
sign-in. After consent, the browser is redirected to
`<redirect_uri>?code=<code>&state=<state>`.

### `POST /api/oauth/token`

Form-encoded body. Two grants:

**`authorization_code`** — exchange a code + PKCE verifier for an access
token and refresh token.

```bash
curl -sS https://api.modelux.ai/api/oauth/token \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d 'grant_type=authorization_code' \
  -d 'code=mlx_oac_...' \
  -d 'client_id=<your-client-id>' \
  -d 'redirect_uri=http://127.0.0.1:8787/callback' \
  -d 'code_verifier=<pkce-verifier>' \
  -d 'resource=https://api.modelux.ai/api/mcp'
```

**`refresh_token`** — rotate a refresh token.

```bash
curl -sS https://api.modelux.ai/api/oauth/token \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d 'grant_type=refresh_token' \
  -d 'refresh_token=mlx_ort_...' \
  -d 'client_id=<your-client-id>'
```

Response:

```json
{
  "access_token": "mlx_oat_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "mlx_ort_...",
  "scope": "mcp:read mcp:write"
}
```

Refresh tokens rotate on every use. Presenting a rotated (already-replaced)
refresh token revokes the whole chain and returns `invalid_grant` — treat
that as "re-run the authorize flow."

### `POST /api/oauth/revoke`

[RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009). Revoke either an
access or refresh token. Unknown tokens return 200 per spec.

```bash
curl -sS -X POST https://api.modelux.ai/api/oauth/revoke \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d 'token=mlx_oat_...'
```

## Using a token against MCP

Every MCP JSON-RPC call presents the access token as a bearer:

```
POST /api/mcp
Authorization: Bearer mlx_oat_...
```

`/api/mcp` validates the audience (`resource`) binding, checks the token
hasn't expired or been revoked, and verifies the token owner is still a
member of their org. Failures return `401` with a `WWW-Authenticate` header
pointing at `/.well-known/oauth-protected-resource` so spec-compliant
clients restart the auth dance without user intervention.

## Token lifetimes

| Token | TTL | Notes |
|---|---|---|
| Authorization code | 10 min | single-use |
| Access token | 1 h | opaque, not JWT; revoke is instant |
| Refresh token | 30 d | rotates on every use; reuse detection revokes the chain |

## Scopes

- `mcp:read` — list / get / describe tools only.
- `mcp:write` — required for any mutating tool (create, update, delete,
  revoke, …).

Scope narrows the token; the underlying dashboard role still gates the
action. A user whose dashboard role is `viewer` cannot escalate by
requesting `mcp:write`.
