Appearance
Authentication
Finblade authenticates API calls with session access tokens. You obtain one by signing in, send it on every request, refresh it before it expires and revoke it when you are done. This is exactly what the web app does; there is no separate API-key mechanism today.
No personal API keys (yet)
Every API client — a script, a BI tool, an integration — signs in as a user and acts as that user, with that user's data and permissions. For unattended integrations create a dedicated service user in your organisation and sign in with its credentials. Webhook triggers are the exception: they use their own credential and need no session (see Webhooks).
The session model
Signing in creates a session with three clocks:
| Token | Lifetime | Where it lives | What it is for |
|---|---|---|---|
| Access token | 24 hours | Response body (auth_token) | Sent on every request as Authorization: Token <token> |
| Refresh token | 7 days, sliding — renewed on every refresh | refresh_token cookie (httpOnly), also accepted in a request body | Exchanged for a new access + refresh pair |
| Absolute lifetime | 90 days from sign-in, never extended | absolute_expires_in in the sign-in response | After this the session cannot be refreshed; sign in again |
Lifetimes are deployment settings (SESSION_ACCESS_TOKEN_EXPIRY_SECONDS, SESSION_REFRESH_TOKEN_EXPIRY_SECONDS, SESSION_ABSOLUTE_LIFETIME_SECONDS); the values above are the defaults. Multiple sessions per user are allowed by default — each device, browser or script gets its own — unless the deployment enables single-session mode, in which case a new sign-in revokes the others.
Signing in
POST /api/v1/token/login/ with username (a username or an email address) and password:
bash
curl -i -X POST "https://api.example.com/api/v1/token/login/" \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{"username": "jane@acme.com", "password": "•••••••••"}'http
HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: refresh_token=9d1c2b3a…; Path=/api/v1/auth/; Max-Age=604800; HttpOnly; Secure; SameSite=None
{"auth_token": "3f9c1b0e4d5a…", "expires_in": 86400, "absolute_expires_in": 7776000}The refresh token is only in the cookie. Non-browser clients must capture the Set-Cookie header (curl's -c cookies.txt, requests.Session in Python, a cookie jar in most HTTP libraries) if they intend to refresh. If you do not, simply sign in again when the access token expires — that is fine for scripts that run for less than a day.
Sign-in is rate-limited to 10 attempts per minute per account. Wrong credentials return 400 with non_field_errors; see the endpoint reference for every error case.
Other ways to obtain a session — passkeys, Microsoft and Google single sign-on — produce the same response shape and cookie. They require a browser for the provider's consent page, so they are not suitable for scripts.
Sending the token
Put the access token in the Authorization header with the Token scheme (case-insensitive; a bare token without the scheme is also accepted):
http
Authorization: Token 3f9c1b0e4d5a…A request without a valid token gets 401 with {"detail": "Authentication credentials were not provided."} or {"detail": "Invalid or expired token."}. A valid token without permission for the resource gets 403 or 404 (many endpoints answer 404 for objects you cannot see, so their existence is not revealed).
Refreshing
Before the access token expires — the web app does it a few minutes early and on any 401 — call POST /api/v1/auth/refresh/:
bash
# with the cookie jar saved at sign-in
curl -s -X POST "https://api.example.com/api/v1/auth/refresh/" -b cookies.txt -c cookies.txt
# without cookies: send the refresh token in the body
curl -s -X POST "https://api.example.com/api/v1/auth/refresh/" \
-H "Content-Type: application/json" \
-d '{"refresh_token": "9d1c2b3a…"}'Every refresh rotates both tokens: the response carries a new access token and sets a new refresh cookie; the old refresh token stops working (after a 30-second grace window that lets concurrent clients settle on the same new pair). The old access token stops working immediately.
Failure modes:
| Response | Meaning | What to do |
|---|---|---|
401 code: invalid_refresh_token | Unknown, expired or already-rotated refresh token | Sign in again |
401 code: max_session_lifetime_exceeded | 90 days since sign-in | Sign in again |
400 code: missing_refresh_token | Neither cookie nor body carried a token | Fix the client |
Signing out
POST /api/v1/auth/logout/revokes the calling session only.POST /api/v1/auth/logout-all/revokes every session of the user — useful after a credential leak, and what "Sign out of all devices" does.
Deactivating a user (for example through SCIM) revokes their sessions too.
Sign-in flow for the web app (for comparison)
The app stores the access token in memory/local storage, relies on the httpOnly refresh cookie for rotation, refreshes proactively, retries a request once after a 401, and treats 403 as "not allowed" rather than "signed out". Follow the same pattern in long-running clients.
Two-factor and verification
Where the deployment enforces email verification, a newly registered account must confirm the one-time code from its welcome email (POST /users/verify/) — and, on some deployments, be approved by an administrator — before token/login/ succeeds. The API's password sign-in has no second factor of its own: the web app can add an authenticator-app (TOTP) step in front of it, and passkeys and SSO provide phishing-resistant alternatives, but a script that holds the password gets a token directly.
Security notes
- Always use HTTPS. Tokens are bearer credentials: anyone holding one is you.
- Never put the access token in a URL. To let a browser or another system fetch a file, use a signed media URL or a permanent link.
- Store refresh tokens like passwords. Prefer re-authenticating over persisting a refresh token in a shared location.
- If you suspect a token has leaked, call
logout-all/(or use "Sign out of all devices" in the app) and sign in again.