# OAuth PKCE: let users connect your app

Your app can get a RouterPlus inference key for a user. The user does not copy a key. The user
signs in, selects a workspace and approves. Then your app receives the key.

The flow is OAuth 2.0 with PKCE (RFC 7636). It uses the common request and response shapes for
PKCE key exchange. To use an existing PKCE client, change its two URLs.

## Steps

1. Make a code verifier: 43 to 128 random characters from `A-Z a-z 0-9 - . _ ~`. Keep it in
   your app.
2. Make the code challenge: `base64url(sha256(verifier))`, with no padding.
3. Send the user's browser to the consent page:

```
https://demo.routerplus.com/auth?callback_url=https://yourapp.example.com/callback&code_challenge=CHALLENGE&code_challenge_method=S256
```

4. The user approves. The browser goes to your `callback_url`, with `?code=CODE` added.
5. From your app, exchange the code for a key:

```bash
curl -s https://demo-api.routerplus.com/api/v1/auth/keys \
  -H "Content-Type: application/json" \
  -d '{"code":"CODE","code_verifier":"VERIFIER","code_challenge_method":"S256"}'
```

```json
{"key": "tm_vk_...", "user_id": "user@example.com"}
```

6. Send the key as a Bearer token on inference calls: `Authorization: Bearer tm_vk_...`.

## Consent page parameters

| Parameter | Required | Meaning |
|---|---|---|
| `callback_url` | yes | Where the browser returns with the code. https only, except plain http on `localhost` and `127.0.0.1`. No user name, password or fragment. Its own query parameters are kept. |
| `code_challenge` | recommended | The PKCE challenge. Without it, the code alone is enough to get the key. |
| `code_challenge_method` | no | `S256` (recommended) or `plain`. Default `plain`. |
| `key_label` | no | The key's name on the user's API keys page, up to 80 characters. Default: your callback host. |
| `limit` | no | A spending limit for the key in USD, for example `25`. The user can change it before approval. |

The user sees your callback host, the workspace list and the limit. Owners and admins can
approve. Viewers cannot approve. A user who is not signed in signs in first and then returns to
the consent page.

## Exchange endpoint

`POST https://demo-api.routerplus.com/api/v1/auth/keys` (also `POST /v1/auth/keys`)

Request body, JSON:

| Field | Required | Meaning |
|---|---|---|
| `code` | yes | The `code` from the callback. |
| `code_verifier` | when a challenge was sent | The verifier you made in step 1. |
| `code_challenge_method` | no | If sent, it must equal the method on the consent request. |

Response `200`: `{"key": "...", "user_id": "<email of the user who approved>"}`. RouterPlus
returns the key one time. Store it in your app.

## Rules

- A code expires 10 minutes after approval.
- A code works one time. A second exchange of the same code fails and makes no key.
- A wrong `code_verifier` fails and leaves the code usable until it expires.
- The key belongs to the workspace that the user selected. Its spend goes to that organization,
  up to the limit.
- The user can disable the key at any time in the console, under Access > API keys.
- If the user stops being an owner or admin before the exchange, the exchange fails. The code
  is then used up.

## Errors

Errors have the shape `{"error": {"code": <status>, "message": "..."}}`.

| Status | Cause |
|---|---|
| `400` | No `code`; the code is unknown, expired or used; or `code_challenge_method` is not `S256` or `plain`, or differs from the consent request. |
| `403` | The `code_verifier` does not match the challenge, or the user or workspace can no longer make keys. |
| `405` | The method is not POST. |
| `503` | A temporary failure. Retry with the same code. |
