This page covers the two halves of access control:Documentation Index
Fetch the complete documentation index at: https://docs.lms.bsa.ai/llms.txt
Use this file to discover all available pages before exploring further.
- 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.
Before you start
You need a partner account — an email + password issued by the Blackswan 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. Token issuance lives on a separate auth host from the main API:| Purpose | Host |
|---|---|
Token issuance (/v1/auth/token) | https://auth-api.bsa.ai |
Everything else (/v1/customers, /v1/loans, etc.) | https://api-staging.bsa.ai |
1. Obtain a token
CallGET /v1/auth/token with HTTP Basic authentication. The
endpoint returns a signed JWT.
Headers
curl -u email:password or
requests.get(..., auth=(email, password)).
Example
Response
kid of the key that signed it (so callers
relying on JWKS verification get the right key), and the payload
carries the standard claims:
Token lifetime
Tokens are valid for 365 days from issuance (exp - iat = 31_536_000).
There’s no refresh endpoint — when expiry approaches, just call
GET /v1/auth/token again with your Basic credentials.
Handling expiry
Pick whichever pattern fits your architecture; they’re not mutually exclusive.Proactive — decode the exp claim
The cleanest approach if you cache the token. JWTs are
header.payload.signature with each segment as base64url-encoded JSON.
Decode the payload, read exp (unix seconds), refresh when the
remaining lifetime drops below a threshold (e.g. 7 days).
Reactive — catch 401, refresh, retry once
Simplest pattern. No JWT parsing required, just one extra round-trip when expiry actually hits.- Don’t retry indefinitely — retry exactly once. If the second call also 401s, the credentials are wrong, not the token.
- A 401 can also mean the token was revoked or your account was disabled — same handler still works (you’ll get 401 again on retry, and surface it).
Scheduled rotation
For machine-to-machine integrations where you already have the Basic credentials stored, the simplest possible setup is a cron that refreshes weekly or monthly. No expiry math, no retry logic — just a fresh token always ready./var/lib/bsa/token (or whatever store you use) on
every request. The cron ensures it’s never within 7 days of expiry.
Knowing when expiry is coming
If you operate the integration yourself rather than via cron, set a calendar reminder for ~11 months after each token issuance. Theiat claim in the JWT payload gives you the exact issuance timestamp
to anchor on.
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 enforces:- Authentication — a valid, unexpired JWT in the
Authorizationheader. Failure:401 unauthenticated. - Authorization — the JWT must contain the
ADMINrole. Failure:401 unauthenticated(the API does not distinguish “not authenticated” from “not authorized” on the wire — both map to 401).
/v1/customers/..., /v1/loans/...,
/v1/repayments/..., /v1/loan-products/..., /v1/credit-decisions/...)
require the ADMIN role.
3. Use the token
Set theAuthorization header on every request:

