In this section: OAuth app

OAuth app

Register a client, send a user through consent, and call Miles on their behalf: the full OAuth 2.1 sequence, with every request and response.

Before you start

There is no app dashboard. Registration is Dynamic Client Registration (RFC 7591) against the MCP server's issuer, one request per client, and the client is public: no client secret is issued and none is accepted. What you register is what you get back; there is no console to edit it later, so re-register if a redirect URI changes.

Every token belongs to the Miles user who approved it, not to your app. Miles never sees a password; consent happens on askmiles.ai where the user's session already is.

1. Discover the issuer

Fetch the authorization server metadata once and read the endpoints from it rather than hardcoding them.

curl https://mcp.askmiles.ai/.well-known/oauth-authorization-server

The document names registration_endpoint, authorization_endpoint, token_endpoint, revocation_endpoint, the supported scopes, and S256 as the only PKCE method.

2. Register the client

curl https://mcp.askmiles.ai/register \ -H "Content-Type: application/json" \ -d '{ "client_name": "Trip planner", "redirect_uris": ["https://example.com/oauth/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none", "scope": "miles:read miles:chat" }'

The response repeats what you sent plus a client_id. Keep it; it is the only credential your app holds.

Redirect URIs must be https, except loopback http://127.0.0.1 and http://localhost for a native or command-line client. At most ten per client. client_name is what the consent screen shows, cut at 40 characters; a logo URI is ignored.

The registration endpoint is rate limited per IP, 20 requests a minute, and the body is capped. Register once and store the result.

3. Send the user to authorize

Generate a PKCE verifier and its S256 challenge, then open:

https://mcp.askmiles.ai/authorize ?response_type=code &client_id=CLIENT_ID &redirect_uri=https://example.com/oauth/callback &scope=miles:read%20miles:chat &code_challenge=CHALLENGE &code_challenge_method=S256 &state=RANDOM &resource=https://mcp.askmiles.ai/mcp

The user lands on a consent page at askmiles.ai listing exactly what your app will be able to see. Read access is on; miles:write is a separate toggle on that screen, off unless the user turns it on, and a token never carries it otherwise. On approval the browser returns to your redirect URI with code and your state.

resource (RFC 8707) names the server the token is for. Leave it at the MCP server URL. A request naming a different resource keeps only the scopes that server serves, and a request that keeps none is refused with invalid_scope rather than silently falling back to miles:read.

4. Exchange the code

curl https://mcp.askmiles.ai/token \ -d grant_type=authorization_code \ -d client_id=CLIENT_ID \ -d code=CODE \ -d redirect_uri=https://example.com/oauth/callback \ -d code_verifier=VERIFIER
{ "access_token": "miles_at_…", "refresh_token": "miles_rt_…", "token_type": "Bearer", "expires_in": 3600, "scope": "miles:read miles:chat" }

Access tokens live one hour. Refresh tokens live thirty days and rotate: each refresh returns a new pair and retires the old refresh token. Every token is prefixed (miles_at_, miles_rt_, miles_ac_ for a code) so a leak is recognisable in a log.

5. Call Miles

With miles:read or miles:write, connect to the MCP server as a client and call tools; see the MCP overview. With miles:chat, call the Chat API exactly as a personal key would:

curl https://askmiles.ai/v1/chat/completions \ -H "Authorization: Bearer miles_at_…" \ -H "Content-Type: application/json" \ -d '{"model": "miles", "messages": [{"role": "user", "content": "Which of my cards earns most on dining?"}]}'

6. Refresh and revoke

curl https://mcp.askmiles.ai/token \ -d grant_type=refresh_token \ -d client_id=CLIENT_ID \ -d refresh_token=miles_rt_…

To end a grant from your side, post the token to the revocation endpoint. The user can end it from theirs at any time at Settings → Connected AI Apps, where your client_name appears with a Disconnect control; a paused account keeps its tokens but refuses every call with connections_paused until the user resumes.

Scopes you can request

ScopeWhat it reachesDefault
miles:readEvery read tool on the MCP serverRequested when you send no scope
miles:writeThe sixteen write tools; ten also need the user to be a MemberOff until the user toggles it on
miles:chat/v1/chat/completions and /v1/modelsOff
stub:read, stub:write, stub:suggestTrips on Stub, a separate product that accepts Miles tokensOff

Other scopes exist for Miles' own apps and are refused to a registered client.