Skip to content
Esc
↑↓navigate↵open⌘Jpreview
On this page

Authentication

Agent reference for token scopes, pairing, claims, recovery, and revocation.

Most HTTP API operations accept Bearer tokens. Some team operations require a session cookie; see Team API credentials. An MCP client with a browser should complete OAuth when it connects, which is the smoothest path and needs no token handling. A client without one can send a chrm_user_* token as its Bearer to an MCP surface instead, which is what pairing exists for. For HTTP request and response schemas, use the REST API reference.

Choose a credential

Use the smallest scope that fits the task.

Credential Scope Use
chrm_app_* One app Returned when an agent creates an anonymous app. It can read, update, and run that app.
chrm_user_* One account Used by agents that need the user’s apps, sharing, secrets, or account settings. Works on the HTTP API and on the MCP surfaces.
chrm_render_* One app at runtime Minted by Charming for window.charming. Agents do not mint or manage it. It is accepted for 24 hours; a page that gets token_expired recovers by reloading.
Session cookie One account Required by the cookie-only team operations. The CLI does not obtain or store it.

See Pricing and limits for token lifetimes.

Discovery

Do not hardcode auth metadata. Use these machine-readable sources:

  • OpenAPI contract for routes, schemas, security rules, and errors.
  • OAuth authorization server for sign-in and pairing endpoints. Its agent_auth.anonymous.credential_types_supported value is ["api_key"], because pairing returns a personal access token after approval.
  • OAuth protected resource for the API’s authorization server.
  • Path-scoped protected-resource metadata for one MCP surface, at /.well-known/oauth-protected-resource followed by the surface path — /mcp, /mcp/chatgpt, /mcp/inline. Each surface names its own document in the WWW-Authenticate header it returns on a 401, and the document’s resource is that surface rather than the origin. A token is bound to the resource it was issued for: ask for resource=https://charm.ing/mcp/chatgpt and the token is honoured at /mcp/chatgpt and refused at /mcp. These three are the only resources the server issues tokens for; any other resource is refused at authorization with invalid_target.
  • Read authorization_servers from the surface document rather than assuming the origin. A surface may name itself, in which case its authorization-server metadata lives at the RFC 8414 path-inserted URL for that issuer — for /mcp/chatgpt that is /.well-known/oauth-authorization-server/mcp/chatgpt. That document may carry a different authorization_endpoint from the origin-wide one, on a host reachable to readers who cannot resolve this one. Everything else, including the token endpoint, stays on this origin. A client that hard-codes the root authorization-server document still works; it just signs in on this host.

Cached /api/auth/mcp/* URLs remain supported as aliases for the corresponding /api/auth/oauth2/* endpoints. The signing-key endpoint is the exception: /api/auth/mcp/jwks maps to /api/auth/jwks. These endpoints serve the same OAuth flow directly, including token refresh, without an endpoint redirect. New connections should use the URLs in discovery metadata. PKCE and credential validation apply to both URL forms.

Team API credentials

The OpenAPI contract and charming api describe OPERATION_ID distinguish team operations by credential: read the security list of each operation. Operations that list userToken accept a personal access token or a session cookie. For teams, those are team listing and creation (list-teams, create-team), app defaults (get-team-app-defaults, update-team-app-defaults), team security (get-team-security, update-team-security), the team inventories (list-team-apps, get-team-audit, list-team-egress, get-team-exposure, list-team-people, list-team-shares), direct member addition (add-team-member), and App transfer (transfer-app). Existing role and plan requirements still apply.

Invitation creation, cancellation, acceptance, decline, and resend; member role changes and removal; and leaving a team require a session cookie. Their sessionCookie security scheme names __Secure-better-auth.session_token for HTTPS; local HTTP uses better-auth.session_token. A personal access token alone gets 401 unauthorized, even when it belongs to an authorized team owner. Logging in again does not help, because a new token is refused the same way. Ask the person to make the change in the Charming web app, or send their existing session cookie with the request; with the CLI, pass --header Cookie=NAME=VALUE. For these operations the CLI sends no Bearer token and does not obtain or store the cookie.

Introspection and userinfo

The authorization-server document advertises these alongside revocation. Both are new.

userinfo_endpoint takes any valid access token as a bearer, of either kind, and returns the person behind it: sub, name, email, email_verified. A token that is not valid gets 401 with invalid_token.

introspection_endpoint (RFC 7662) requires the calling client to authenticate with a secret, which no client registered through Dynamic Client Registration has: those are public clients using PKCE, and they receive 401 with invalid_client. A confidential client that does authenticate learns only about its own tokens. A token belonging to another client and a token that never existed both answer {"active": false}, so introspection cannot be used to test whether a token exists.

Revocation is the exception among the three: it needs only a client_id, so a public client can revoke.

Revoking an MCP connection

The authorization-server document advertises a revocation_endpoint (RFC 7009). It answers differently depending on what you send, and the difference decides whether a disconnection takes effect at once:

  • The refresh token: revoked, 200. No further access tokens can be minted, so this is what ends a connection.
  • An access token issued for no resource: revoked, 200.
  • An access token issued for a resource: refused, 400 with unsupported_token_type. A resource-bound token is signed rather than stored, so there is nothing to revoke; the server says so instead of reporting a success that did not happen.

So revoke the refresh token when a person disconnects. A signed access token they already hold stays valid until it expires, up to an hour, and no request to this endpoint shortens that.

Anonymous app token

Create an app without an Authorization header to receive a chrm_app_* token. Keep it secret. It works only for the returned app and cannot list other apps.

An anonymous app expires unless a user claims it. Claiming the app revokes its chrm_app_* token. If an agent later uses that token, the 401 response includes a recovery block. Follow that block instead of creating a replacement app.

User token

A signed-in user can mint a chrm_user_* token with Create token in Account settings → Agents, or with Create token. Choose a lifetime of 1 to 365 days (expiresInDays); the API defaults to 7, and Settings offers 7, 30, 90 days or a custom date. Only a sign-in session can choose a lifetime other than 7 days: a request authenticated with a chrm_user_* token can create only 7-day tokens and gets 403 forbidden for a longer one. A label is at most 80 characters. Charming returns the plaintext once. Use List tokens to inspect active tokens and Revoke token to revoke one.

An agent without a browser session should pair:

  1. Call Start pairing.
  2. Show the returned user_code and verification_url to the user.
  3. Poll with Poll pairing at the returned interval.
  4. Store the chrm_user_* token returned after approval. Charming returns it once. Collect it within 24 hours of approval; after that the poll returns expired and the token is revoked.

Send that token as the Bearer on either transport: the HTTP API, or an MCP surface such as /mcp.

The device_code is private to the agent. The user_code is safe to show to the user.

Claim an anonymous app

There are two supported flows:

  • Set pair: true when you create an anonymous app. Approval pairs the agent and claims the app in one flow.
  • If you already have a chrm_user_* token, call Claim app with the app’s chrm_app_* token as proof.

Both flows revoke the app token after claim. A claim also approves a pairing bound at create with pair: true. That token lasts 30 days when a person approves in the browser and 7 days when a bearer token claims the app or approves the code. Use the user token for later work. A claim can answer 409 already_claimed for an app that is already yours, because approving the code at /pair also claims it. Read the app with your user token before treating that 409 as a failure.

Send a token

HTTP agents send Authorization: Bearer <token>. Do not put a user or app token in a URL. Follow build-http.md for request examples.

Rendered apps call their backend through api from charming:ui/[email protected] instead of managing credentials. Charming attaches the render token.

Recover from auth errors

  • 401 means the request has no valid credential. Follow error.recovery when present.
  • 403 means the credential is valid but lacks access to the app or action. Do not retry with the same scope.
  • token_revoked_post_claim means an app was claimed. Pair for a user token, then retry.
  • token_expired includes the next auth step in error.recovery.
  • account_not_joined (403) means the account signed in through someone else’s App and has not joined Charming. It can use Apps, including an App’s API and its feedback, and the widget’s viewer actions, and it can accept or decline a Direct share. Any other change it asks for is refused with this kind, such as creating, copying, or taking over an app, minting or pairing a token, connecting an agent, joining a team, or changing account settings. Ask the person to open the Join page named in error.message, choose a username, and accept the Terms, then retry.
  • sign_in_required (401) means a claimed app refused a gated call from a caller it cannot name. Inside a rendered app, window.charming.withUser(action) signs the visitor in and re-runs the action; an HTTP agent sends a user token instead. Signing in fixes this one, unlike unauthorized.

The OpenAPI contract defines every error field and reason.

Revoke access

Users can review and revoke agent tokens from the account menu: choose Account settings, then Agents. HTTP agents can use List tokens and Revoke token. The list covers minted and paired tokens. To rotate without a browser, mint the new token, list with it to find the old token’s id, then revoke the old one. Revocation applies on the next request, including for the token that sent the revoke call.

Claiming an anonymous app revokes its app token. Render tokens rotate when Charming renders the app again.

Was this page helpful?