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.

This walkthrough assumes you have already registered your OAuth2 app and have your client_id and client_secret. Pick one environment — sandbox or production — and use it throughout.

Step 1 — Generate PKCE and CSRF values

Your backend generates a fresh PKCE verifier and CSRF state for every authorization request. Never reuse them.
# Generate code_verifier
CODE_VERIFIER=$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')

# Derive code_challenge = base64url(sha256(code_verifier))
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=')

# Generate CSRF state (at least 16 random bytes)
STATE=$(openssl rand -hex 16)
Store CODE_VERIFIER and STATE in the user’s server-side session — you’ll need them in steps 3 and 4.

Step 2 — Redirect the user to Busha

Build the authorization URL and redirect the user’s browser to it. This is the only step that touches the browser.
https://login.busha.io/oauth2/auth
  ?response_type=code
  &client_id=busha-oauth2-10d3cdb5-dda3-451a-87fc-9d6ccb0bb691-live
  &redirect_uri=https%3A%2F%2Fyourapp.example.com%2Foauth2%2Fcallback
  &scope=openid+offline_access+balances%3Aread+transactions%3Aread
  &state=YOUR_STATE
  &code_challenge=YOUR_CODE_CHALLENGE
  &code_challenge_method=S256
ParameterRequiredValue
response_typecode
client_idYour app’s client ID
redirect_uriByte-identical to a registered URI
scopeSpace-separated list of scopes
stateCSRF guard — bind to the user’s session
code_challengebase64url(sha256(code_verifier))
code_challenge_methodS256plain is not accepted
The user sees Busha’s hosted login and consent screens. When they click Authorize, Busha redirects their browser back to your callback.

Step 3 — Handle the callback

Busha redirects the user to:
https://yourapp.example.com/oauth2/callback?code=AbCdEf123XYZ&state=YOUR_STATE
Before doing anything with the code:
  1. Verify state matches the value you stored in step 1. If it doesn’t match, reject the request — this is a CSRF attack.
  2. Deduplicate the code if your callback could fire twice (e.g. the user double-clicks). Codes are single-use; a second exchange attempt returns invalid_grant.
If the user clicks Cancel on the consent screen, Busha sends ?error=access_denied&state=... instead. Render a “continue without connecting” path.

Step 4 — Exchange the code for tokens

This call is server-to-server only. Never expose your client_secret or the token response to the browser.
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=authorization_code" \
  --data-urlencode "code=$AUTH_CODE" \
  --data-urlencode "redirect_uri=https://yourapp.example.com/oauth2/callback" \
  --data-urlencode "code_verifier=$CODE_VERIFIER"
Successful response:
{
  "access_token":  "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token": "rt_5dG9HaP2mQ8kVnY4...",
  "id_token":      "eyJhbGciOiJSUzI1NiIs...",
  "token_type":    "bearer",
  "expires_in":    3600,
  "scope":         "openid offline_access balances:read transactions:read"
}
TokenReturned when
access_tokenAlways
refresh_tokenoffline_access scope was granted
id_tokenopenid scope was granted
Store access_token and refresh_token securely. See Token handling for storage requirements and rotation.

Step 5 — Call the Busha API

Attach the access token as a Bearer header:
curl https://api.busha.io/v1/balances?currency=NGN \
  -H "Authorization: Bearer $ACCESS_TOKEN"
{
  "data": [
    { "currency": "NGN", "amount": "12345.67", "available": "12345.67" }
  ]
}
You are now calling Busha on the user’s behalf.

Step 6 — Keep the session alive with refresh

Access tokens expire in ~1 hour. Before exp, swap the refresh token for a new pair:
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"
Refresh tokens rotate on every use. Persist the new (access_token, refresh_token) pair before discarding the old one. If persistence fails mid-call, you lose the session and must prompt the user to re-authorize. If you replay a previously-used refresh token, the entire token family is revoked immediately.

What’s next

Scopes reference

Choose the right scopes for your integration and understand what each one does.

Token handling

Validation, storage, rotation, and revocation in depth.

Endpoints

Full reference for all OAuth2 protocol endpoints.

Errors

Every error code, what causes it, and how to fix it.