Sign in with Xava
Let people sign in to your app with their Xava Inference account, read their balance, and spend their credits on models or marketplace tools within a monthly cap they choose.
Standard OAuth 2.1 authorization code with PKCE (S256 only). Register your app in My Clients to get a client ID. MCP clients that register themselves (dynamic registration or a Client ID Metadata Document) use the same server: see MCP sign-in.
Register your app
- In My Clients: a name (1 to 80 characters), your app's https website, 1 to 10 callback URLs (https, http on a loopback address with any port, or a native app scheme; matched exactly), the permissions the client may ask for, and optionally one of your community apps.
- Type: a server app gets a client secret (
client_secret_basic; keep it on your backend); a browser or native app is a public PKCE client with no secret. - The secret is shown once. Rotate it any time: the old secret stops working at once, and access tokens already issued last until they expire. Disable a client to stop new sign-ins and disconnect every user.
- Up to 50 clients per account. Your client's page shows how many tool calls succeeded through it and what they charged in total, never a user's balance.
- Changing a client's permissions or required tools can ask its users to connect again.
Permissions
| scope | spends credits | what the user is shown |
|---|---|---|
profile | no | Know which account you are: your account name (email, X handle or wallet) and Reward Status |
inference | yes | Run text, image, video and audio models, charged to your balance (within the monthly cap) |
account:read | no | See your balance and usage |
tools:call | yes | Call the marketplace tools listed below, charged to your balance (within the monthly cap) |
A registered client that asks for no scope gets profile account:read. Asking for a scope your client is not allowed is invalid_scope, and a client with required tools must ask for tools:call.
The flow
# 1. send the user to the consent screen (fresh state, S256 code challenge)
https://xavainf.com/oauth/authorize?response_type=code&client_id=xc_...
&redirect_uri=https://app.example/callback&state=...
&scope=profile%20account:read%20tools:call
&code_challenge=<43 chars>&code_challenge_method=S256&resource=https://xavainf.com
# 2. exchange the code (single use, valid 5 minutes) on your backend
curl https://xavainf.com/oauth/token -u "xc_...:<client secret>" \
-d grant_type=authorization_code -d code=... -d code_verifier=... \
-d redirect_uri=https://app.example/callback
# {"access_token":"xat_...","token_type":"Bearer","expires_in":3600,"refresh_token":"xrt_...","scope":"..."}
# 3. who signed in (profile), and their balance (account:read)
curl https://xavainf.com/oauth/userinfo -H "Authorization: Bearer xat_..."
# {"sub":"<account id>","name":"...","reward_status":"noob","holder":false,"status":"active",
# "balance":{"available_usd":"...","held_usd":"...","topup_url":"https://xavainf.com/dashboard/billing"}}
curl https://xavainf.com/oauth/account -H "Authorization: Bearer xat_..."
# balance + "connection": {monthly_cap_usd, spent_this_month_usd, remaining_budget_usd, period_ends_at, ...}- Access tokens last 1 hour; refresh tokens last 30 days and rotate on every use. Reusing an old refresh token disconnects the app. A refresh cannot change the permissions.
- The
resourceis this site's origin (the default for registered clients),/mcp,/mcp/accountor/v1. Use the origin when the app calls both the account endpoints and MCP. - Each connection can make up to 600 requests per minute. Revoke a token at
/oauth/revoke;/oauth/connectionshows the connection's budget and its accepted tools. - With
inference, send the access token as the key on the OpenAI-compatible API.
Consent and the monthly cap
- The user sees "Allow <your app> to use your Xava Inference account?", your website, what the app will be able to do, and where they will be sent back. Your app's name is shown as you registered it, with "The app chose its own name."
- An app that only reads (
profile,account:read) gets no spending question. - An app that spends gets a monthly spending cap: $20, $50 (the default), $100, $250, $500, no cap. No cap allows spending up to the available balance. The user must also tick that they allow your app to spend their balance, purchased credits included. The cap resets on the first of each month at 00:00 UTC.
- Models, uploads and tool calls through your app share that one monthly budget per user. The user's own monthly usage limit, if set, applies too.
- Each required tool is listed with its description, "Required by this app" and its price per call, and whether it needs the user's own account with the tool's provider. If you also built that tool, the screen says that the app's developer earns the listed tool fee.
- Users change the cap, see the accepted tools or disconnect your app in their dashboard under API keys, Connected apps.
Calling marketplace tools
- In your client's settings, choose the tools it requires: up to 100 exact tools (no wildcards), from tools approved and listed on Connections. Your own private servers cannot be required. Enable the
tools:callpermission first. - Send
tools/callto/mcp/accountwith the access token: one tool call per request, with anIdempotency-Keyheader of 1 to 200 visible ASCII characters, unique per operation. Without it the request is refused with HTTP 400. - Retries never run the tool twice. The same key with the same tool and arguments returns a receipt with the operation's
state(in_progress,succeeded,failedoroutcome_unknown). Arguments and results are not stored, so a retry cannot return the result. The same key with different input isidempotency_conflict. - If the provider's outcome is unknown (a timeout, for example), the operation will not run again by itself: check with the provider before you start a new one with a new key.
- Your app can call only the tools the user accepted that are still on your client's list and still approved. If a tool's price rises above what the user accepted, or its definition changes, the call returns
consent_required: send the user through sign-in again. - Tools that need the user's own account with a provider (for example Google Calendar) have a separate connection step;
/oauth/connectionreturns each tool'sprovider_stateand asetup_url. - Calls are charged at the tool's listed price and only when the tool succeeds (see how tool calls are billed).
curl https://xavainf.com/mcp/account \
-H "Authorization: Bearer xat_..." \
-H "Idempotency-Key: order-1234-create-event" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"<server>__<tool>","arguments":{}}}'Errors
| where | error | meaning |
|---|---|---|
| API, uploads | 402 spending_cap_reached | the connection reached its monthly cap; the body has cap_usd, spent_this_month_usd, required_usd and manage_url |
| API, uploads | 402 monthly_limit_reached | the user's own monthly usage limit would be passed |
| API | 403 insufficient_scope | the connection was not granted inference |
| API | 403 oauth_policy_changed | the connection changed or was disconnected; connect again |
| MCP | 401 invalid_token | the token expired or was revoked: refresh it, or sign in again |
| MCP | 403 insufficient_scope | the connection lacks the scope the call needs (tools:call for marketplace tools) |
| MCP | 400, JSON-RPC -32600 | no Idempotency-Key, or more than one tool call in the request |
| tool result | monthly spending cap | isError with the cap in the message, plus manage_url and required_usd: not an HTTP 402 |
| tool result | low balance | isError with required_usd, available_usd and topup_url |
| tool result | idempotency_conflict | this key was already used for a different tool or arguments |
| tool result | operation_pending | not confirmed yet; retry with the same key |
| tool result | outcome_unknown | the provider's outcome is unknown; it will not run again automatically |
| tool result | consent_required | the tool's terms or the connection changed; the user must connect again |
| tool result | tool_unavailable | the tool is not accepted for this connection, or no longer approved |
| /oauth/userinfo | 403 insufficient_scope | the connection was not granted profile |
Tool-result errors carry code in structuredContent where shown. API errors use the shape in errors & rate limits.
What you earn
Builders earn from their own published tools: each charged call of your approved community tool earns you your fee, through your app or anyone else's (see payouts). Requiring someone else's tool does not change who earns its fee. Nothing is paid to an app's developer for what users spend on models through the app.