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. 1. Discover the endpoints

    curl https://awardtravelfinder.com/.well-known/oauth-authorization-server

    Returns the authorization_endpoint, token_endpoint, and registration_endpoint (RFC 8414). If you got here from a 401 on /mcp, the WWW-Authenticate header points at /.well-known/oauth-protected-resource/mcp (RFC 9728) instead, which names this authorization server.

  2. 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_id for a public client (token_endpoint_auth_method: "none"). Redirect hosts must be on the MCP allowlist (Claude, ChatGPT, Cursor, loopback) unless your operator extends OAUTH_DCR_REDIRECT_HOST_ALLOWLIST. Scope stays fixed at mcp:read (plus openid and email on the registered client) regardless of what you ask for.

  3. 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=S256 is the only method accepted, and PKCE itself is mandatory — a public client can't skip it by omitting code_verifier at redemption time.

  4. 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. 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=$VERIFIER

    Returns an access_token and a refresh_token.

  6. 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. 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.