Skip to content

OAuth 2.0 ​

Xiaoya uses the authorization-code flow to grant a third-party agent the minimum permissions approved by the current user. Resource APIs revalidate the user, agent, scope, Team membership, and context version on every request.

Authorization request ​

http
GET /oauth/authorize

Use response_type=code, the registered client_id and exact redirect_uri, a space-separated scope, and a validated state. Public clients use PKCE S256. Team authorization should explicitly include the target team_id.

text
https://claw.zhenhuiai.com/oauth/authorize?response_type=code&client_id=agent-client&redirect_uri=https%3A%2F%2Fagent.example.com%2Foauth%2Fcallback&scope=profile%20balance%20model&state=<random-state>&code_challenge=<s256-challenge>&code_challenge_method=S256

The Xiaoya App open flow attaches a single-use code to the registered entry URL. The code expires after five minutes. Do not render third-party resources before exchanging it; finish with a 303 redirect to a clean URL without the code.

Scopes ​

ScopeCapability
profileCurrent subject and visible Team contexts
balancePersonal or exact Team balance
chargeDirect charge and refund
usageAgent-attributed Personal usage
modelModel catalog and model gateway
team_readTeam read access
team_manageOwner/Admin Team management
skillLicensed runtime assets
sandboxCloud sandbox runtime

The default scopes are only profile balance charge usage; model and Team clients must explicitly request the additional scopes they use.

Lifecycle and isolation ​

  • Authorization code: five minutes, single use.
  • New access token: 7 days. Previously issued tokens keep their original expiry. Developer sessions must not outlive the returned expires_in.
  • New exchanges do not issue refresh tokens; legacy refresh tokens are compatibility only.
  • A user, agent, membership, role, Team, or context-version change can invalidate existing credentials.
  • Store Personal and Team sessions separately, keyed by client, subject, context type, and exact Team ID.

Exchange the code ​

Confidential server clients submit client_secret. Public clients do not store a secret and submit the matching PKCE code_verifier instead.

bash
curl --request POST 'https://claw.zhenhuiai.com/oauth/token' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode 'client_id=agent-client' \
  --data-urlencode 'client_secret=<server-only-secret>' \
  --data-urlencode 'code=<single-use-code>' \
  --data-urlencode 'redirect_uri=https://agent.example.com/oauth/callback'
js
const body = new URLSearchParams({
  grant_type: 'authorization_code',
  client_id: process.env.XIAOYA_CLIENT_ID,
  client_secret: process.env.XIAOYA_CLIENT_SECRET,
  code,
  redirect_uri: 'https://agent.example.com/oauth/callback'
});
const response = await fetch('https://claw.zhenhuiai.com/oauth/token', {
  method: 'POST',
  headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },
  body,
  redirect: 'error',
  signal: AbortSignal.timeout(10_000)
});
const token = await response.json();
if (!response.ok) throw new Error(`OAuth failed: ${token.error}`);

Personal token response:

json
{
  "access_token": "<server-only-token>",
  "token_type": "Bearer",
  "expires_in": 604800,
  "scope": "profile balance charge usage model"
}

A Team token additionally contains its exact binding:

json
{
  "access_token": "<server-only-team-token>",
  "token_type": "Bearer",
  "expires_in": 604800,
  "scope": "profile balance model team_read",
  "team_id": "team_01",
  "team_authorization_id": "authorization_01"
}
FieldMeaning
access_tokenBearer credential stored only on a trusted server
token_typeAlways Bearer
expires_inLifetime in seconds; currently 604800
scopeActually granted scopes, which may differ from those requested
team_idExact Team binding, returned only for a Team token
team_authorization_idRevocable Team authorization record

OAuth failures do not use the platform envelope:

json
{
  "error": "invalid_grant",
  "error_description": "Authorization code is invalid or expired"
}

Common values are invalid_client, invalid_grant, and unsupported_grant_type.

Revoke a token ​

bash
curl --request POST 'https://claw.zhenhuiai.com/oauth/revoke' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'client_id=agent-client' \
  --data-urlencode 'client_secret=<server-only-secret>' \
  --data-urlencode 'token=<access-token>' \
  --data-urlencode 'token_type_hint=access_token'

A successful revocation returns HTTP 200. Clear the developer session even if the token was already inactive.

Never place a token, code, or client secret in browser storage, a URL, an AI prompt, logs, or error details. Browser code should hold only the agent application's own secure HttpOnly session cookie.

See the complete API reference for every OAuth parameter and response schema.

OAuth credentials and model-provider secrets must remain on trusted servers.