OAuth endpoints
Discovery, registration, authorization, tokens and revocation, for building your own MCP client.
Claude and ChatGPT do all of this for you. This page is for anyone building their own MCP client.
Discovery
An unauthenticated POST to the MCP endpoint answers 401 with a WWW-Authenticate header naming where the metadata lives.
The challenge
# POST https://app.prepaid.ai/api/mcp with no token answers:## HTTP/1.1 401# WWW-Authenticate: Bearer# resource_metadata="https://app.prepaid.ai/.well-known/oauth-protected-resource/api/mcp",# scope="workspace:read"GET/.well-known/oauth-protected-resource/api/mcpRFC 9728 protected resource metadata. Names the authorization server.
GET/.well-known/oauth-authorization-serverRFC 8414 authorization server metadata. Names the endpoints below.
Registration
POST/api/oauth/registerRFC 7591 dynamic client registration. No authentication.
- redirect_urisstring[]required
- HTTPS, or
httpon loopback for a desktop app. No fragments; at most ten. - client_namestringoptional
- Shown to the person on the approval screen.
- client_uristringoptional
- A link to the client, shown beside its name.
You get back a client_id and no secret — token_endpoint_auth_method is none. PKCE proves a token request is yours.
Authorization
GET/api/oauth/authorizeChecks the request, then sends the person to the approval screen in their console.
- client_idstringrequired
- From registration.
- redirect_uristringrequired
- Must match a registered one exactly.
- response_typestringrequired
- Only
code. - code_challengestringrequired
- The S256 challenge for your verifier.
- code_challenge_methodstringrequired
- Only
S256. - scopestringoptional
- Only
workspace:read, the default. - statestringoptional
- Returned to you untouched.
- resourcestringoptional
- RFC 8707. The token is stamped with it and refused anywhere else.
Tokens
POST/api/oauth/tokenExchanges a code for tokens, or refreshes them. Form-encoded.
Exchanging a code
curl https://app.prepaid.ai/api/oauth/token \ -d grant_type=authorization_code \ -d code=the_code_you_received \ -d code_verifier=your_pkce_verifier \ -d client_id=your_client_id \ -d redirect_uri=https://your.app/callbackResponse
{ "access_token": "…", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "…", "scope": "workspace:read"}- A code is spent the first time it is presented.
grant_type=refresh_tokenrotates: the refresh token you sent is revoked and a new pair is issued.- Errors are RFC 6749 codes —
invalid_grant,invalid_request— with a sentence inerror_description.
Revocation
POST/api/oauth/revokeRFC 7009. Form-encoded, with the token to revoke.
Revoking any token of a connection ends the whole connection. It answers 200 for a token we have never seen, as the RFC requires.