> Fetch https://askmiles.ai/llms.txt first: it indexes every public Miles capability. This file is the markdown twin of https://askmiles.ai/docs/getting-started/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.

```bash
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

```bash
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:

```text
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

```bash
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
```

```json
{
  "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](https://askmiles.ai/docs/mcp/overview). With `miles:chat`, call the Chat API exactly as a personal key would:

```bash
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

```bash
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](https://askmiles.ai/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

| Scope | What it reaches | Default |
|---|---|---|
| `miles:read` | Every read tool on the MCP server | Requested when you send no scope |
| `miles:write` | The sixteen write tools; ten also need the user to be a Member | Off until the user toggles it on |
| `miles:chat` | `/v1/chat/completions` and `/v1/models` | Off |
| `stub:read`, `stub:write`, `stub:suggest` | Trips on Stub, a separate product that accepts Miles tokens | Off |

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