---
title: "MCP OAuth token endpoint proxy (authorization_code + refresh_token)"
method: POST
path: "/oauth/token"
tags: ["OAuth", "MCP"]
---

# MCP OAuth token endpoint proxy (authorization_code + refresh_token)

`POST /oauth/token`

## Request

- Base URL: `https://api.apphandoff.com/api`
- URL: `https://api.apphandoff.com/api/oauth/token`
- Auth: none required

Forwards `authorization_code` and `refresh_token` grants to Supabase
`/auth/v1/oauth/token`. Discovered as `token_endpoint` from
`/.well-known/oauth-authorization-server` for MCP clients.

Accepts both `application/x-www-form-urlencoded` (OAuth 2.1 standard) and
`application/json` request bodies.

**RFC 8707 (Resource Indicators):** clients MAY include an optional
`resource` parameter. The canonical value is the production MCP endpoint
(`https://api.apphandoff.com/mcp`). During the compatibility transition,
the API base URL and legacy `/api/mcp-bot` endpoint remain accepted as
exact aliases; all other values return `invalid_target` (HTTP 400). The
value is forwarded upstream so RFC 8707-aware issuers can populate the
JWT `aud` claim.

## Request body

- union
  - OAuthRefreshTokenGrant
    - `grant_type` 'refresh_token', required
    - `refresh_token` string, required
    - `client_id` string, required: Supabase OAuth client UUID or an HTTPS Client ID Metadata Document URL.
    - `resource` string, uri: RFC 8707 resource indicator. Optional; the canonical value is the `/mcp` endpoint. The API base URL and legacy `/api/mcp-bot` endpoint remain accepted as exact transition aliases. Every other value returns `invalid_target`.
  - OAuthAuthorizationCodeGrant
    - `grant_type` 'authorization_code', required
    - `code` string, required
    - `client_id` string, required: Supabase OAuth client UUID or an HTTPS Client ID Metadata Document URL.
    - `redirect_uri` string, required: Must be `https://`, `http://localhost`/`127.0.0.1`/`[::1]`, or a registered custom app scheme. `javascript:`, `data:`, `file:`, `vbscript:` are denylisted.
    - `code_verifier` string, required: PKCE verifier; the AS metadata advertises S256-only.
    - `resource` string, uri: RFC 8707 resource indicator (see refresh_token grant).

## Response `200`

Token payload from Supabase

- object

## Other responses

- `400`: Invalid request. Local validation failures use the `Error` envelope (`INVALID_BODY` — schema validation failure; `INVALID_RESOURCE` — `resource` does not match the canonical API URL). Failures from the grant exchange itself use `OAuthTokenError`; in particular a dead or unknown refresh token returns `invalid_grant`, which means re-authorize, not retry.
- `429`: Rate limited
- `500`: Server error
- `502`: Upstream Supabase token exchange error, including a network failure reaching it. Reported as `server_error` — retryable, unlike a token fault.
- `503`: CIMD mapping lookup failed (`temporarily_unavailable`).

---

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