Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.busha.io/llms.txt

Use this file to discover all available pages before exploring further.

Token types at a glance

TokenFormatLifetimeNotes
access_tokenJWT (RS256)~1 hourValidated locally. Cannot be revoked mid-flight — it self-expires at exp.
refresh_tokenOpaque string30 days, slidingSingle-use. Rotates on every refresh. Replay revokes the entire family.
id_tokenJWT (RS256)~1 hourIdentity proof only. Do not use as a bearer token on API calls.

Validating access tokens

Access tokens are JWTs signed with RS256. Validate them locally on every request — no per-request introspection round-trip to Busha.
1

Fetch and cache the JWKS

Fetch public keys from <issuer>/.well-known/jwks.json. Cache for ~5 minutes. Refetch when you encounter a kid (key ID) that is not in your cache.
curl https://login.busha.io/.well-known/jwks.json
Read the canonical issuer string from /.well-known/openid-configuration at startup and pin against it — do not hardcode.
2

Match the key and verify the signature

Match the JWT header’s kid to a key in the JWKS. Verify the RS256 signature with that key.
Never decode an unverified JWT and trust its claims. A forged token looks identical until you check the cryptographic signature.
3

Verify the standard claims

ClaimCheck
issMust match the canonical issuer from the discovery document.
expMust be in the future. Allow ~30 seconds of clock skew.
audMust include your client_id (if present).
client_idMust match your registered client_id.
4

Enforce scopes on every endpoint

Check the scp claim (a space-separated string or array depending on your library) against the scope required by the endpoint being called. A token with scp="balances:read" cannot call a transfers endpoint.

Refreshing tokens

Every successful refresh issues a new (access_token, refresh_token) pair and immediately invalidates the old refresh token.
curl -X POST https://login.busha.io/oauth2/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=refresh_token" \
  --data-urlencode "refresh_token=$REFRESH_TOKEN"
Rotation rules:
  • Persist the new pair atomically before discarding the old one. If persistence fails, you lose the session.
  • If a previously-rotated refresh token is replayed, Busha revokes the entire token family immediately.
  • Any invalid_grant response on a refresh is a hard disconnect — prompt the user to re-authorize.
  • The 30-day lifetime is sliding: each successful refresh resets the clock by another 30 days.

Revoking tokens

When a user disconnects your integration, revoke the refresh token:
curl -X POST https://login.busha.io/oauth2/revoke \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "token=$REFRESH_TOKEN" \
  --data-urlencode "token_type_hint=refresh_token"
The endpoint returns 200 with an empty body whether or not the token was valid — this is intentional and prevents leaking token validity.
Revoking a refresh token prevents future refreshes immediately, but does not invalidate a currently-issued access token. The access token continues to validate until its exp (~1 hour). For instant revocation on critical paths, consider maintaining a server-side allowlist that your backend checks.

Storage requirements

TokenStorage requirement
access_tokenIn-memory or short-lived server-side cache. Never in browser localStorage or cookies.
refresh_tokenEncrypted at rest, with row-level encryption keyed per user. Never in browser cookies or localStorage.
client_secretSecret manager (HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager). Never in source code or environment variables committed to version control.

What never to log

Never write these values to application logs, request logs, or error trackers:
  • client_secret
  • access_token
  • refresh_token
  • code_verifier
  • Authorization code
  • id_token
Safe to log: client_id, jti, sub, scope list, request IDs, trace IDs.