Endpoints
All endpoints are listed in the discovery document.Clients
Create a client in the API Dashboard on the CabalSpy ID tab. The Instance ID is theclient_id (csid_...).
Confidential clients (server-side apps) authenticate with a client secret (csid_sk_...). Only a hash of the secret is stored, so copy it when you create it. Several secrets can be active at once: when you roll a secret the old one stays valid for a time you choose, so you can deploy the new one without downtime.
Public clients (browser apps, mobile apps) have no secret and use none as authentication method. PKCE protects their flow.
Redirect URIs must be registered and match exactly. Live instances need every redirect URI and web origin on a verified domain.
Scopes
An instance can only request the scopes enabled for it in the dashboard. The user approves the scopes once per app. Asking for more scopes later shows the consent screen again.
Authorization request
Create a randomcode_verifier (43 to 128 characters), its S256 code_challenge, a random state and a random nonce, then redirect the user.
string
required
Always
code.string
required
Your Instance ID.
string
required
A registered redirect URI, matched exactly.
string
required
Space-separated scopes. Must contain
openid.string
required
Base64url SHA-256 of the code verifier (43 characters).
string
required
Always
S256.string
Random value you check on the callback. Up to 500 characters.
string
Random value that comes back in the ID token. Up to 300 characters.
string
none signs in silently or returns login_required or consent_required. consent always shows the consent screen. none cannot be combined with other values.code, state and iss. Check that state matches and that iss is https://id.cabalspy.xyz. The code is single use and valid for 60 seconds.
Token request
Exchange the code at the token endpoint with the code verifier.Response
refresh_token is only returned when offline_access was granted.
Tokens
Verify ID tokens with the keys at
https://id.cabalspy.xyz/oauth/jwks. Check the signature (ES256, select the key by kid), iss = https://id.cabalspy.xyz, aud = your client_id, exp, and nonce if you sent one. New signing keys are published in the JWKS before they are used, so cache the JWKS but refresh it when you see an unknown kid.
JWKS
Refresh tokens
Withoffline_access you get a refresh token. Exchange it for new tokens at the token endpoint.
invalid_grant when the user removed access to your app.
Revocation
Revoke a refresh token when the user signs out of your app. This ends its whole family. Unknown tokens are not an error.User info
sub and, with profile, name, preferred_username, picture and kol_badge. The full x, wallets and onchain claims come from the user info endpoint.
Claims
string
Stable user id. Use it as the user’s key in your database. It is the
csid_id in the Data API.string
Display name: from X, otherwise from Telegram or Google. Scope
profile.string
The X handle without the @. Users without X get
tg.<telegram username> or id.<10 hex characters>, see Users without X. Scope profile.string
Avatar URL from X or Google, can be null. Scope
profile.string
Public CabalSpy ID profile URL. Scope
profile.boolean
Whether the user holds the reviewed KOL badge. Always false without X. Scope
profile.object
X account details, or null when the user has no X account connected. Scope
profile.array
Verified wallets. Scope
wallets.object
Latest on-chain volume check, or null. Scope
onchain.boolean
Present and true for made-up sandbox test users. Never trust such an identity in production.
Users without X
People sign in to CabalSpy ID with X, with Telegram through the CabalSpy ID bot, or with Google as an alternative. Nothing changes in the flow for your app, but users who signed in with Telegram or Google and have not connected X look different:
Handles of users without X always contain a dot, X handles never do. When a user connects X later,
preferred_username, name, picture and x switch to their X account while sub stays the same, so always key your users on sub, never on the handle.
User info without X
Limited users
Your plan sets how many users you receive data for. Everyone can always sign in, but above the limit the person gets an anonymoussub that starts with anon_ and no profile, wallet or on-chain data. The ID token and the user info then look like this:
sub is stable for that person and your domain, so you can still keep an account for them. After you upgrade, people who sign in again receive their real sub and data. See Plans and limits.

