# Scopes and claims (/docs/auth/scopes)

Every scope auth.codai.ro issues, what the user sees on the consent screen, which claims and resources each one unlocks.

Request scopes space-separated in the `scope` parameter of the authorize request. The consent screen shows the user the description in the second column, verbatim. Standard OIDC scopes return claims in the ID token and at `GET /me`; codai scopes are **resource scopes** — they carry no claims, they decide what the access token may do at `https://ai.codai.ro`.

## Scopes [#scopes]

| Scope | Consent screen text | Unlocks |
| --- | --- | --- |
| `openid` | Verify your codai identity | Required. Claims `sub`, `role`, `sid`. |
| `profile` | See your name and profile picture | Claims `name`, `picture`. |
| `email` | See your email address | Claims `email`, `email_verified`. |
| `inference` | Run AI inference through your codai account | Lets the access token mint a gateway key at [`/connect/key`](./connect-flow.mdx#mint-the-gateway-key). Resource `https://ai.codai.ro/v1`. |
| `keys:manage` | Create and manage codai API keys on your behalf | Also entitles `/connect/key`. Request it when your app manages the key's lifecycle rather than only using it. |
| `usage:read` | Read your codai usage statistics | Resource scope on `https://ai.codai.ro/v1`. |
| `subscriptions:read` | Read your codai subscription plan | Resource scope on `https://ai.codai.ro/v1`. |
| `mcp` | Use codai tools and memory from an MCP client | Only against resource `https://ai.codai.ro/mcp`; the access token becomes an RS256 JWT the gateway verifies locally. |
| `offline_access` | Stay signed in on this device | A rotating refresh token — issued only if the client also has the `refresh_token` grant. |

For the connect flow the canonical request is `openid email profile inference keys:manage`. Ask for what you use; the user reads the list.

## Resources and token format [#resources-and-token-format]

`auth.codai.ro` implements RFC 8707 resource indicators. The `resource` parameter (default `https://ai.codai.ro/v1`) selects which resource server the access token is for; a token is scoped to the intersection of what you requested and what that resource accepts.

| Resource | Scopes it accepts | Access token |
| --- | --- | --- |
| `https://ai.codai.ro/v1` (default) | `inference keys:manage usage:read subscriptions:read` | Opaque, 1 h |
| `https://ai.codai.ro/mcp` | `mcp` | RS256 JWT, 1 h, `aud` = the resource, carries `role` |

You do not have to think about this for the connect flow — omit `resource` and you get the default.

## Claims [#claims]

Claims live in the ID token and are returned by `GET /me` with a valid access token.

| Claim | From scope | Value |
| --- | --- | --- |
| `sub` | `openid` | The user's stable codai id (UUID). Key every record you store on this, never on `email`. |
| `role` | `openid` | The account role, e.g. `user`. Present in the ID token even when an access token is issued alongside — codai does not strip non-standard claims. Also inside MCP JWT access tokens. |
| `sid` | `openid` | The IdP session id. Match it against the `sid` in a back-channel `logout_token` to end the right local session. |
| `name` | `profile` | Display name. |
| `picture` | `profile` | Avatar URL. |
| `email` | `email` | Email address. |
| `email_verified` | `email` | Boolean. |
| `acr`, `amr` | — (always) | How the user authenticated this session. |

### Authentication context [#authentication-context]

The IdP stamps every ID token with how the user signed in:

| Method | `amr` | `acr` |
| --- | --- | --- |
| Password | `["pwd"]` | `urn:codai:acr:pwd` |
| Password + TOTP | `["pwd", "otp"]` | `urn:codai:acr:mfa` |
| Passkey | `["webauthn"]` | `urn:codai:acr:passkey` |
| Magic link | `["email"]` | `urn:codai:acr:pwd` |
| Google | `["google"]` | `urn:codai:acr:pwd` |

To *require* a level, send `acr_values` on the authorize request — `urn:codai:acr:mfa` for a second factor, `urn:codai:acr:passkey` for a passkey. The IdP re-prompts when the current session's method is weaker or its login is older than 10 minutes, then returns the achieved `acr`. Check it in the ID token; do not assume.

## What a token does *not* carry [#what-a-token-does-not-carry]

- No organisation or team claim — orgs are a [shared-sessions](../sessions/sharing-and-orgs.mdx) concept on the gateway, queried with the gateway key.
- No plan or billing information — that is behind `subscriptions:read` at the gateway, not in the ID token.
- No API key. The key comes only from `/connect/key`, only once.
