---
title: "Exchange a code, or refresh a connection"
method: POST
path: "/oauth/token"
tags: ["Connecting an app"]
---

# Exchange a code, or refresh a connection

`POST /oauth/token`

## Request

- Base URL: `https://verifiedhandles.org`
- URL: `https://verifiedhandles.org/oauth/token`
- Auth: none required

The OAuth 2.1 token endpoint, for MCP clients a person has connected to their account. `grant_type=authorization_code` redeems the one-time code the person’s consent sent the client (it works once, for 60 seconds), with the PKCE `code_verifier` whose S256 challenge came with the authorization request, the same `redirect_uri` and `client_id`, and optionally the `resource` (the MCP address the consent was for). `grant_type=refresh_token` swaps the current refresh token for a new pair; the old pair stops working at once. A code presented a second time, or a refresh token that has been replaced, ends the connection: one copy of it was stolen. The connection acts as the person, never above the role ceiling they chose or their role of the moment, read-only if they chose that, until the 7, 30 or 90 days they chose; a refresh never extends it. Public clients only: send `client_id`, and no `Authorization` header, `client_secret` or `client_assertion` (each is refused `invalid_client`, 401).

## Response `200`

A new access and refresh token.

- OAuthTokens: A new access and refresh token for one connection.
  - `access_token` string, required: Send it as `Authorization: Bearer`, to the MCP address it was issued for.
  - `token_type` 'Bearer', required
  - `expires_in` integer, required: Seconds the access token works: an hour, or less when the connection ends sooner.
  - `refresh_token` string, required: Use it once, for the next pair. Presenting an old one again ends the connection.
  - `scope` 'mcp', required

## Other responses

- `400`: `invalid_request`: not a form, a field sent twice, or a required field missing. `unsupported_grant_type`. `invalid_grant`: the code or refresh token is unknown, expired, already used or replaced, was issued to another client or `redirect_uri`, the verifier does not match, or the person may no longer connect apps at that ceiling. `invalid_target`: the `resource` is not the one the consent named.
- `401`: `invalid_client`. Public clients only: send `client_id`, and no `Authorization` header, `client_secret` or `client_assertion` (each is refused `invalid_client`, 401).
- `429`: Too many requests from this IP address in the last minute (at a route that takes a key, an admin key’s are counted per key instead, at the limits set on Console → Rate limits). Wait the number of seconds `Retry-After` gives.
- `503`: Connecting apps is switched off for now (`temporarily_unavailable`). (While the site is down for maintenance, the answer is the maintenance page, as HTML.)

---

[API](https://skmtc.dev/verifiedhandles/apis/verified-handles-api.md) · [All operations](https://skmtc.dev/verifiedhandles/apis/verified-handles-api/llms.txt) · [OpenAPI document](https://skmtc.dev/verifiedhandles/apis/verified-handles-api/revisions/40720de2e0ec?raw)
