> This page is for API Reference, version v1.
> For other versions, use one of these documentation indexes:
> - v0 (default): https://docs.credal.ai/api-reference/v-0/llms.txt
> - v1: https://docs.credal.ai/api-reference/v-1/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.credal.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.credal.ai/_mcp/server.

## OAuth Setup

API v1 uses the OAuth 2.0 Authorization Code flow with **PKCE** (Proof Key for Code Exchange) for authentication. Only **confidential clients** are supported: your server holds a `client_secret` and presents it at the token endpoint. PKCE is also required: include `code_challenge` and `code_challenge_method=S256` in the authorization URL, and `code_verifier` in the token exchange.

This page walks through the full flow: registering a client, directing the user to Credal to authorize, exchanging the resulting code for an access token, and calling the API.

### 1. Register an OAuth client

Creating an OAuth client requires approval from your organization's admin. To request one, ask your admin to contact [support@credal.ai](mailto:support@credal.ai).

After registration you'll receive a **client ID** and **client secret**. Keep the client secret secure — treat it like a password.

### 2. Direct the user to Credal to authorize

First, generate a PKCE pair: a high-entropy random `code_verifier` and its `code_challenge`, the SHA-256 hash of the verifier encoded as base64url without padding. See [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636#section-4.1) for the full requirements. Store the verifier server-side, keyed by `state` — you'll need it in step 3.

#### Python

```python
import base64, hashlib, secrets

code_verifier = secrets.token_urlsafe(64)
code_challenge = (
    base64.urlsafe_b64encode(hashlib.sha256(code_verifier.encode()).digest())
    .rstrip(b"=")
    .decode()
)
```

#### TypeScript (Node)

```typescript
import { createHash, randomBytes } from "crypto";

const codeVerifier = randomBytes(64).toString("base64url");
const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
```

Then send the user to the Credal authorization endpoint. They'll be asked to sign in (if not already) and approve the requested scopes.

```
https://app.credal.ai/api/oauth/authorize
  ?response_type=code
  &client_id=<your_client_id>
  &redirect_uri=<your_redirect_uri>
  &scope=api:agent:message:*
  &state=<random_value>
  &code_challenge=<code_challenge>
  &code_challenge_method=S256
```

| Parameter               | Description                                                                                                                                                                               |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `response_type`         | Always `code`                                                                                                                                                                             |
| `client_id`             | Your client ID from step 1                                                                                                                                                                |
| `redirect_uri`          | The URL Credal redirects back to after authorization (must match your registered redirect URI exactly)                                                                                    |
| `scope`                 | Space-separated list of scopes. `api:agent:message:*` lets you send messages to agents.                                                                                                   |
| `state`                 | A random, unguessable value you generate (e.g. a UUID). Credal echoes it back in the redirect — your callback must verify it matches before exchanging the code, to prevent CSRF attacks. |
| `code_challenge`        | **Required.** The base64url-encoded SHA-256 hash of your `code_verifier`. Requests without it are rejected.                                                                               |
| `code_challenge_method` | **Required.** Must be `S256`. The `plain` method is not accepted.                                                                                                                         |

After the user approves, Credal redirects to your `redirect_uri` with a short-lived authorization code:

```
https://your-app.com/callback?code=<authorization_code>&state=<random_value>
```

### 3. Exchange the code for an access token

**The authorization code is not an access token.** You must exchange it by making a server-side POST request to the token endpoint. Do not skip this step.

The request must include both your `client_secret` and the `code_verifier` from step 2. Credal checks each independently, and the exchange fails if either is missing or invalid.

In your callback handler, use the `state` value from the redirect to retrieve the `code_verifier` you stored in step 2, and abort if none exists for that `state`. Each login attempt has its own verifier, so a server handling concurrent logins must resolve it per request. See [oauth.net's PKCE overview](https://oauth.net/2/pkce/) for background.

#### curl

```bash
curl -X POST https://app.credal.ai/api/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=<authorization_code>" \
  -d "client_id=<your_client_id>" \
  -d "client_secret=<your_client_secret>" \
  -d "redirect_uri=<your_redirect_uri>" \
  -d "code_verifier=<code_verifier>"
```

#### Python

```python
import requests

response = requests.post(
    "https://app.credal.ai/api/oauth/token",
    data={
        "grant_type": "authorization_code",
        "code": authorization_code,
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "redirect_uri": REDIRECT_URI,
        "code_verifier": code_verifier,
    },
)
tokens = response.json()
access_token = tokens["access_token"]
refresh_token = tokens["refresh_token"]
```

#### TypeScript

```typescript
const response = await fetch("https://app.credal.ai/api/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "authorization_code",
    code: authorizationCode,
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET,
    redirect_uri: REDIRECT_URI,
    code_verifier: codeVerifier,
  }),
});
const { access_token, refresh_token } = await response.json();
```

The response contains the tokens you'll use to call the API:

```json
{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "eyJ..."
}
```

A few things to note:

* The authorization code expires in **10 minutes** and is **single-use**. Exchange it immediately after receiving it.
* The `redirect_uri` must match the one used in step 2 exactly.
* The `code_verifier` must be the exact value whose SHA-256 hash you sent as `code_challenge` in step 2.

### 4. Call the API

Pass the `access_token` to the Credal SDK:

#### Python

```python
import credal

client = credal.CredalClient(token=access_token)

response = client.agents.send_message(
    agent_id="your-agent-id",
    conversation=credal.NewConversation(),
    message="Hello!",
)
```

#### TypeScript

```typescript
import { CredalClient } from "@credal/sdk";

const credal = new CredalClient({
  token: accessToken,
  environment: "https://app.credal.ai/api/v1",
});

const response = await credal.agents.sendMessage({
  agentId: "your-agent-id",
  conversation: { type: "new" },
  message: "Hello!",
});
```

### 5. Refresh the access token

Access tokens expire after 1 hour. Use the `refresh_token` to get a new one without sending the user through the browser flow again:

#### curl

```bash
curl -X POST https://app.credal.ai/api/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=<your_refresh_token>" \
  -d "client_id=<your_client_id>" \
  -d "client_secret=<your_client_secret>"
```

#### Python

```python
response = requests.post(
    "https://app.credal.ai/api/oauth/token",
    data={
        "grant_type": "refresh_token",
        "refresh_token": refresh_token,
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
    },
)
tokens = response.json()
access_token = tokens["access_token"]
refresh_token = tokens.get("refresh_token", refresh_token)  # Credal rotates refresh tokens; persist the new value
```

Refresh tokens are valid for **30 days** and are rotated on every use — each refresh response includes a new `refresh_token` that replaces the old one. Always persist the new value; the previous token is immediately invalidated. If a refresh token expires, the user will need to go through the browser authorization flow again.