---
name: oauth
description: Prizmad OAuth 2.1 — Authorization Code + PKCE + Dynamic Client Registration is the primary path (powers Claude Desktop / Claude.ai / ChatGPT / Cursor "Connect" flow). client_credentials remains for headless / programmatic clients.
version: 2.0.0
---

# Prizmad OAuth 2.1

Prizmad publishes a full OAuth 2.1 authorization server with Dynamic Client Registration. Three grant types are supported, each for a different audience:

| Grant | When | Output |
|---|---|---|
| `authorization_code` (PKCE S256, public client) | Interactive MCP / connector flow — Claude Desktop, Claude.ai, ChatGPT, Cursor, Zed | RS256 JWT, audience `https://prizmad.com/api/mcp`, 1 h, refresh-rotated |
| `refresh_token` | Companion to authorization_code | Same shape; old refresh is single-use, reuse revokes the chain |
| `client_credentials` | Headless / server-to-server using an API key as `client_secret` | HS256 JWT, audience `https://prizmad.com/api`, 1 h |

The MCP server also accepts the raw API key (`przmad_sk_live_...`) as a Bearer token directly, without going through OAuth at all.

## Discovery

- **Authorization server metadata**: <https://prizmad.com/.well-known/oauth-authorization-server>
- **Protected resource metadata**: <https://prizmad.com/.well-known/oauth-protected-resource>
- **JWKS**: <https://prizmad.com/.well-known/jwks.json>

## Authorization Code + PKCE (the "Connect" flow)

This is what runs when a user clicks **Add custom connector → enter `https://prizmad.com/api/mcp`** in Claude Desktop, Claude.ai, ChatGPT, Cursor, etc. The client implements RFC 7591 / 8414 / 9728 / 7636 / 8707; no manual app registration is needed.

```text
1. POST /api/mcp without auth
   ← 401 + WWW-Authenticate: Bearer resource_metadata="…"

2. GET /.well-known/oauth-protected-resource
   GET /.well-known/oauth-authorization-server

3. POST /oauth/register
   Body (RFC 7591):
     {
       "client_name":  "<your app>",
       "redirect_uris": ["<your callback URL>"],
       "grant_types":   ["authorization_code", "refresh_token"],
       "response_types": ["code"],
       "token_endpoint_auth_method": "none"
     }
   ← 201 { "client_id": "mcp_…", … } (no client_secret — public client)

4. Browser to /oauth/authorize?
       response_type=code
     & client_id=<from step 3>
     & redirect_uri=<from step 3>
     & code_challenge=<S256(verifier)>
     & code_challenge_method=S256
     & scope=videos:read videos:write
     & resource=https://prizmad.com/api/mcp
     & state=<csrf>
   User signs in (NextAuth) and approves on the consent screen.
   ← 302 redirect_uri?code=…&state=…

5. POST /oauth/token
     grant_type=authorization_code
     code=<from step 4>
     redirect_uri=<from step 3>
     client_id=<from step 3>
     code_verifier=<original PKCE verifier>
     resource=https://prizmad.com/api/mcp
   ← 200 { "access_token": "<RS256 JWT>", "refresh_token": "…", "expires_in": 3600, "token_type": "Bearer" }

6. POST /api/mcp with Authorization: Bearer <access_token>
   ← MCP traffic.
```

Refresh:

```bash
curl -X POST https://prizmad.com/oauth/token \
  -d grant_type=refresh_token \
  -d refresh_token=<previous refresh_token> \
  -d client_id=<client_id>
```

Returns a fresh access token + a new refresh token; the previous refresh is single-use. Reusing a revoked refresh token revokes the entire chain (defence against token theft).

## client_credentials (headless / programmatic)

Quickest way to turn a long-lived API key into a short-lived bearer for server-to-server calls.

### Form-encoded

```bash
curl -X POST https://prizmad.com/oauth/token \
  -d grant_type=client_credentials \
  -d client_id=my-app \
  -d client_secret=przmad_sk_live_...
```

### JSON

```bash
curl -X POST https://prizmad.com/oauth/token \
  -H "Content-Type: application/json" \
  -d '{"grant_type":"client_credentials","client_id":"my-app","client_secret":"przmad_sk_live_..."}'
```

### Basic Auth

```bash
curl -X POST https://prizmad.com/oauth/token \
  -u "my-app:przmad_sk_live_..." \
  -d grant_type=client_credentials
```

### Response

```json
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "videos:read videos:write"
}
```

## Scopes

| Scope | Description |
|-------|-------------|
| `videos:read` | Read video status, projects, assets |
| `videos:write` | Create videos, upload images, mutate projects |

## Notes

- All endpoints (`/oauth/authorize`, `/oauth/token`, `/oauth/register`) live on the same origin as the MCP server (`https://prizmad.com`). This sidesteps the Claude.ai web bug that ignores cross-origin authorization endpoints.
- Access tokens expire after 1 hour. Refresh tokens last 30 days, single-use with rotation.
- API keys (`przmad_sk_live_...`) continue to work directly as Bearer tokens — OAuth is only needed when the client requires it.
- Rate limit: 10 token requests per minute per identity.
- `client_secret` field, when used, is your Prizmad API key.
- `client_id` for `client_credentials` is a free-form identifier.
