OAuth 2.1 · PKCE
Authenticating against the MCP API
The /mcp endpoint is OAuth 2.1 only — no API keys, no headers to leak. Every MCP client (Claude, Cursor, ChatGPT) registers itself and runs the same PKCE flow described below.
Register a client
Two ways in, depending on who's registering.
MCP clients (automatic)
Claude Desktop, Claude Code, Cursor, and mcp-remote all register themselves the first time they connect, via
Dynamic Client Registration (RFC 7591) at
POST /oauth/register. No sign-up, no plan, no manual step — you'll see this happen automatically if you follow the
MCP quickstart.
Your own client (manual)
Building your own confidential client (a server-side integration, not an MCP client)? Register a named application with a
client_id + client_secret at
/dashboard/api-apps/new. This requires signing in on a Premium plan or higher — the free tier covers connecting MCP clients, not registering new ones. Not signed in, or need a client for something the dashboard doesn't cover? Email
support@awardtravelfinder.com.
Scopes
One scope exists today.
| Scope | Grants |
|---|---|
| mcp:read | Every MCP tool call — search, pricing, portfolio reads and writes. This is the only scope Doorkeeper issues; it's requested automatically and can't be widened. |
There is no mcp:write scope. Tools that mutate data (bookings, points balances, route monitors) run under mcp:read the same as everything else — a write scope isn't offered until per-tool scope enforcement ships.
PKCE flow, step by step
Every step below hits the real metadata endpoints. Run it as-is.
-
1. Discover the endpoints
curl https://awardtravelfinder.com/.well-known/oauth-authorization-serverReturns the
authorization_endpoint,token_endpoint, andregistration_endpoint(RFC 8414). If you got here from a 401 on/mcp, theWWW-Authenticateheader points at/.well-known/oauth-protected-resource/mcp(RFC 9728) instead, which names this authorization server. -
2. Register a client (skip if you already have one)
curl -X POST https://awardtravelfinder.com/oauth/register \ -H "Content-Type: application/json" \ -d '{ "client_name": "My App", "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"] }'Returns a
client_idfor a public client (token_endpoint_auth_method: "none"). Redirect hosts must be on the MCP allowlist (Claude, ChatGPT, Cursor, loopback) unless your operator extendsOAUTH_DCR_REDIRECT_HOST_ALLOWLIST. Scope stays fixed atmcp:read(plusopenidandemailon the registered client) regardless of what you ask for. -
3. Generate a PKCE pair (S256 only)
VERIFIER=$(openssl rand -base64 32 | tr -d '=+/' | cut -c1-64) CHALLENGE=$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=') echo "verifier=$VERIFIER challenge=$CHALLENGE"code_challenge_method=S256is the only method accepted, and PKCE itself is mandatory — a public client can't skip it by omittingcode_verifierat redemption time. -
4. Send the user to authorize
open "https://awardtravelfinder.com/oauth/authorize?\ response_type=code&\ client_id=$CLIENT_ID&\ redirect_uri=http://localhost:8080/callback&\ scope=mcp:read&\ code_challenge=$CHALLENGE&\ code_challenge_method=S256"Opens a normal sign-in + consent screen in the browser. Not signed in yet? It routes through Devise first and comes straight back. Approve once and re-authorization is skipped on future connects from the same client.
-
5. Exchange the code for a token
curl -X POST https://awardtravelfinder.com/oauth/token \ -d grant_type=authorization_code \ -d code=$CODE \ -d redirect_uri=http://localhost:8080/callback \ -d client_id=$CLIENT_ID \ -d code_verifier=$VERIFIERReturns an
access_tokenand arefresh_token. -
6. Call the MCP endpoint
curl -X POST https://mcp.awardtravelfinder.com/mcp \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' -
7. Refresh when it expires
curl -X POST https://awardtravelfinder.com/oauth/token \ -d grant_type=refresh_token \ -d refresh_token=$REFRESH_TOKEN \ -d client_id=$CLIENT_ID
Token lifetime & rate limits
Token lifetime
Access tokens expire after 1 hour. Refresh tokens don't expire on a timer — use the refresh_token grant to mint a new access token instead of re-running the full authorize step. Revoke either from
your applications page or the standard
/oauth/revoke endpoint.
Rate limits
API usage is metered per calendar month against your account's api_tier_limit:
- Free — 50 calls/month
- Premium and above — 150 calls/month
- Enterprise — 500 calls/month by default, negotiable
MCP tool calls carry a separate, much higher anti-abuse ceiling on top of this (measured per day, not per month) so a normal integration never sees it.