Skip to main content

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:
  1. Getting a token — proving who you are with a username/password and receiving a JWT.
  2. 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:
PurposeHost
Token issuance (/v1/auth/token), authenticate, authorizehttps://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.

Headers

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

HTTPCause
401Missing 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)
405Using anything other than GET

2. How authorization works

Every partner-facing endpoint in the wrappers API is gated by two middleware layers:
  1. 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.
  2. 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

RuleWho satisfies it
rule_admin_onlyAny 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"