PrepaidAIDocsv1
StatusConsole

OAuth endpoints

Discovery, registration, authorization, tokens and revocation, for building your own MCP client.

1 min read

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 http on 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/callback
Response
{  "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_token rotates: 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 in error_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.