---
title: "Exchange an authorization code or refresh token for an access token"
method: POST
path: "/oauth/token"
tags: ["Authentication"]
---

# Exchange an authorization code or refresh token for an access token

`POST /oauth/token`

## Request

- Base URL: `https://riskmodels.app/api`
- URL: `https://riskmodels.app/api/oauth/token`
- Auth: none required

OAuth 2.0 token endpoint. Supports exactly two grants — `authorization_code` (PKCE S256, single-use codes) and `refresh_token` (rotating). There is **no** `client_credentials` grant; presenting one returns `unsupported_grant_type`.

The issued `access_token` is an `rm_user_*` API key valid for 1 hour, usable as `Authorization: Bearer …` against any endpoint in this spec. The `refresh_token` is valid for 30 days and rotates on every use — replaying an already-rotated refresh token revokes the whole token family for that user/client pair (RFC 6819 §5.2.2.3).

Accepts either `application/x-www-form-urlencoded` or `application/json`. Responses are `Cache-Control: no-store`. Rate limited to 60 requests per IP per minute.

## Request body

- OAuth2TokenRequest: Shape depends on `grant_type`. For `authorization_code`: `code`, `redirect_uri`, `client_id`, `code_verifier` are all required. For `refresh_token`: `refresh_token` and `client_id` are required.
  - `grant_type` 'authorization_code' | 'refresh_token', required: `client_credentials` is not supported and returns `unsupported_grant_type`.
  - `code` string: Single-use authorization code (authorization_code grant).
  - `redirect_uri` string, uri: Must match the URI the code was issued for (authorization_code grant).
  - `client_id` string, uuid: Client id from `POST /api/oauth/register`.
  - `code_verifier` string: PKCE verifier; must hash (S256) to the registered code_challenge.
  - `refresh_token` string: Rotating refresh token (refresh_token grant).

## Response `200`

Access token issued

- OAuth2TokenResponse
  - `access_token` string: An `rm_user_*` API key. Use directly as `Authorization: Bearer …` against any endpoint in this spec.
  - `token_type` 'Bearer'
  - `expires_in` integer: Access token lifetime in seconds (3600 = 1 hour).
  - `refresh_token` string: Valid 30 days and rotated on every use. Replaying a rotated token revokes the whole family for that user/client pair.
  - `scope` string

## Other responses

- `400`: `invalid_request` (missing parameters), `invalid_grant` (code/refresh token not found, expired, already used, or PKCE/client mismatch), or `unsupported_grant_type`.
- `429`: Token rate limit exceeded
- `500`: server_error — token issuance failed

---

[API](https://skmtc.dev/riskmodels/apis/riskmodels-api.md) · [All operations](https://skmtc.dev/riskmodels/apis/riskmodels-api/llms.txt) · [OpenAPI document](https://skmtc.dev/riskmodels/apis/riskmodels-api/revisions/1a034cad408a?raw)
