Documentation Index
Fetch the complete documentation index at: https://docs.api.bsa.ai/llms.txt
Use this file to discover all available pages before exploring further.
This page covers the two halves of access control:
- Getting a token — proving who you are with a username/password and
receiving a JWT.
- Authorization — what that token lets you do, and which rules
each endpoint is gated by.
Once you have a token, every subsequent request to the wrappers API
uses it as documented in Authentication.
Before you start
You need a partner account — an email + password issued by the BSA
integration team. If you don’t have one yet, contact
hello@bsa.ai. Accounts come with the admin
role attached, which is what every partner-facing endpoint requires.
The wrappers service does not issue tokens itself — token issuance
lives in a separate auth service that fronts the deployment. The
flow is:
client → GET /v1/auth/token (Basic auth) → auth service
↓ JWT
client → GET /v1/customers (Bearer JWT) → wrappers service
↓ verify with auth
↓ enforce admin rule
→ response
1. Obtain a token
Token issuance lives on the auth host, separate from the wrappers
host:| Purpose | Host |
|---|
Token issuance (/v1/auth/token), authenticate, authorize | https://auth-api.bsa.ai |
Everything else (/v1/customers, /v1/loans, etc.) | https://api-dev.bsa.ai |
The same partner credentials work against both — the wrappers host
validates the bearer it receives by calling the auth host transparently.
GET /v1/auth/token with HTTP Basic authentication. The service
authenticates your email/password against the user store, signs a JWT
with whichever signing key is currently active server-side, and
returns it. You don’t pick the key — the service does.
Authorization: Basic <base64(email:password)>
Most HTTP clients do this for you — curl -u email:password or
requests.get(..., auth=(email, password)).
Example
curl -sf "https://auth-api.bsa.ai/v1/auth/token" \
-u "ada@example.com:correct-horse-battery-staple"
Response
{
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjU0YmIyMTY1...XYZ"
}
The header carries the kid of the key that signed it (so callers
relying on JWKS verification get the right key), and the payload
carries the standard claims:
{
"iss": "service-project",
"sub": "5cf37266-3473-4006-984f-9325122678b7",
"iat": 1779659075,
"exp": 1811195075,
"roles": ["ADMIN"]
}
Token lifetime
Tokens are valid for 365 days from issuance (exp - iat = 31_536_000).
The wrappers service does not refresh tokens — when expiry approaches,
request a new one the same way.
Errors
| HTTP | Cause |
|---|
| 401 | Missing Basic header, malformed credentials, wrong password, or account disabled. Response body is always {"code":"unauthenticated","message":"invalid credentials"} regardless of which check failed (no user-enumeration leak) |
| 405 | Using anything other than GET |
2. How authorization works
Every partner-facing endpoint in the wrappers API is gated by two
middleware layers:
- Authenticate — extract the Bearer token from the request, call
the auth service to verify it, and attach the user’s claims (subject
ID, roles, expiry) to the request context. Failure:
401 unauthenticated.
- Authorize — check that the claims contain a role that satisfies
the endpoint’s rule. For all partner endpoints, the rule is
rule_admin_only. Failure: 401 unauthenticated (the wrappers API
does not distinguish “not authenticated” from “not authorized” on
the wire — both map to 401).
You don’t call the authorize step manually. It happens transparently on
every request when the wrappers service evaluates the route’s
middleware chain.
Rules used in the API
| Rule | Who satisfies it |
|---|
rule_admin_only | Any caller whose JWT claims contain the ADMIN role |
All partner-facing endpoints (/v1/customers/..., /v1/loans/...,
/v1/repayments/..., /v1/loan-products/..., /v1/credit-decisions/...)
use rule_admin_only.
3. Use the token
Set the Authorization header on every request:
TOKEN=$(curl -sf "https://auth-api.bsa.ai/v1/auth/token" \
-u "$EMAIL:$PASSWORD" | jq -r .token)
curl -sf "https://api-dev.bsa.ai/v1/customers" \
-H "Authorization: Bearer $TOKEN"
For the per-call mechanics — headers, expiry handling, common 401
causes — see Authentication.
Putting it together
# 1. Get a token (once per ~year, or whenever the current one expires)
TOKEN=$(curl -sf "$AUTH_URL/v1/auth/token" \
-u "$EMAIL:$PASSWORD" | jq -r .token)
# 2. Use it everywhere
curl -sf "$API_URL/v1/customers/42" \
-H "Authorization: Bearer $TOKEN"