Authentication
Audience: 🔌 Integration Client
This page covers the API key, the data-plane credential your integration uses. The admin session (login, 2FA, and how a key is first issued) is a one-time operator/admin task; it lives in the Operate track under Signing in & 2FA.
BankConnector has two credentials you actually use, split along a clean line:
| Credential | Header | Plane | What it's for |
|---|---|---|---|
| API key | X-API-Key: key_… | data plane | submitting payments, reading journal / reconciliation / statements, webhooks |
| Admin session | X-Session-Token: … | control plane | creating companies, creating/managing users, bank connections, approval policies |
The distinction is deliberate and enforced: an API key cannot create companies or users, or set up connections. Those are workspace-admin actions and return 403. Payments are a machine job; platform administration is a User one.
If you provisioned a sandbox (POST /sandbox/provision), the response hands you both: the apiKey, and the dashboardLogin creds (subdomain/email/password) you log in with to get an admin session. (Sandbox tenants, and their credentials, are auto-deleted after 30 days of inactivity; provision a fresh one any time.)
The API key (data plane)
Your integration's day-to-day credential. A single header:
X-API-Key: key_<64 hex characters>
Key facts:
- The key is platform-bound and carries 256 bits of entropy (
key_+ 64 hex). - The key implies your platform. There is no tenant header and you do not pass
platformId; the server infers it from the key. You _do_ passcompanyId(which of your companies the request is for), in the body or query. A request whose stated scope doesn't match the key's platform is rejected (403 forbidden). - No expiry, no TTL, no refresh. A key is valid until revoked. Rotation is manual: create a new key, cut over, revoke the old one.
- Stored only as a SHA-256 digest server-side. It's shown to you once, at creation. Lose it and you rotate; it cannot be recovered.
⚠️ Scope reality: an API key is pinned to a platform, and
companyIdis free within that platform. If your company shares a platform with others, your key can address them by passing a differentcompanyId. For hard isolation, be the only company on your own platform. Confirm your tenancy model with your operator.
What an API key cannot do
API keys drive machine tasks: payments, webhooks, exports. They are deliberately blocked (403) from control-plane / customer-decision routes that must be an authenticated User's action:
- Creating companies (
POST /platforms/<id>/companies) - Bank connection setup and activation
- Approval-policy management
They also can't call session-only identity routes (/auth/me, /me/sessions, /2fa/*): those return 401 for a key.
User provisioning (capped): an API key can headlessly provision operators. It can create
approver/viewerusers (POST /users) and re-role among non-admin roles, so an ERP can onboard its own staff without a User in the dashboard. But a key is deliberately capped so it can't escalate to the control plane: it cannot create or grantadmin, set a password directly (its users are invite-only), reset a password, or erase a user. Those need an admin session. Still treat production keys as privileged: store them in a secret manager and rotate on staff changes.
Why: these are platform-administration and payment-safety decisions. Keeping them off the API key means a leaked integration key can move payments within its scope but can't reshape your tenant (mint users, spin up companies, or wire a new bank connection). To do those, an admin logs in with a session, covered in the Operate track, Signing in & 2FA.
Where does the key come from? An API key is minted once by a workspace admin, and only its metadata is listable afterwards. If you provisioned a sandbox the key is already in the
POST /sandbox/provisionresponse. The admin issuing flow (and the session auth behind it) is in Signing in & 2FA.
Required headers for a machine client
For a normal data-plane JSON request you need only:
X-API-Key: key_…
Content-Type: application/json # for JSON bodies (some routes take raw XML)
Idempotency-Key: <key> # on POST /journal/payments, see Idempotency
You do not send cookies, CSRF tokens, or an Origin. Server-to-server callers send no browser Origin, so the origin guard passes automatically. CSRF applies only to cookie-based browser sessions.
Optional headers worth knowing
| Header | Purpose |
|---|---|
BankConnector-Version: YYYY-MM-DD | pin the API version (baseline 2026-06-01). Echoed back on every response, including errors. A malformed date is a 400. |
X-Request-ID | set by the server on every response. Log it; support can look up any request by it. |
Idempotency-Replayed: true | response header telling you a payment was a replay, not a fresh execution. |
Auth failures
| Situation | Status | code |
|---|---|---|
| Missing / invalid credential | 401 | unauthorized |
Credential valid but wrong scope (e.g. a companyId outside the key's platform) | 403 | forbidden |
API key on most control-plane routes (e.g. POST /platforms/<id>/companies, approval policies) | 403 | forbidden |
API key on the bank-connection routes (/connections, /connections/<id>/…) | 403 | admin_required |
API key on a session-only identity route (/auth/me, /me/sessions, /2fa/*) | 401 | unauthorized |
⚠️ Don't branch on admin_required alone. Most control-plane denials come back as plain 403 forbidden; only the /connections/* family emits admin_required. Branch on the 403 status and treat both codes the same way: you need an admin session, and retrying with the API key will never succeed.
A 401 means re-authenticate (check the key or log in again). A 403 means you'll never get in with this request as written: an API key on a control-plane route is a 403 no matter how many times you retry; switch to an admin session.