sandbox.bankconnector.com API Reference

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:

CredentialHeaderPlaneWhat it's for
API keyX-API-Key: key_…data planesubmitting payments, reading journal / reconciliation / statements, webhooks
Admin sessionX-Session-Token: …control planecreating companies, creating/managing users, bank connections, approval policies

Building a third-party add-on a customer installs into their own account, rather than an integration you run with a key you hold yourself? See "Application installs (OAuth 2.0, for add-on developers)" further down this page instead — a different, narrower credential the customer authorizes and can revoke on their own.

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 /api/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_ pass companyId (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 companyId is free within that platform. If your company shares a platform with others, your key can address them by passing a different companyId. 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 /api/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/viewer users (POST /api/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 grant admin, 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 /api/sandbox/provision response. 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 /api/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

HeaderPurpose
BankConnector-Version: YYYY-MM-DDpin the API version (baseline 2026-06-01). Echoed back on every response, including errors. A malformed date is a 400.
X-Request-IDset by the server on every response. Log it; support can look up any request by it.
Idempotency-Replayed: trueresponse header telling you a payment was a replay, not a fresh execution.

Auth failures

SituationStatuscode
Missing / invalid credential401unauthorized
Credential valid but wrong scope (e.g. a companyId outside the key's platform)403forbidden
API key on most control-plane routes (e.g. POST /api/platforms/<id>/companies, approval policies)403forbidden
API key on the bank-connection routes (/connections, /connections/<id>/…)403admin_required
API key on a session-only identity route (/auth/me, /me/sessions, /2fa/*)401unauthorized

⚠️ 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.


Application installs (OAuth 2.0, for add-on developers)

Audience: add-on / ERP-plugin developers, distinct from everything above. If you operate your own integration with your own API key, skip this section — it's for a third-party add-on a customer installs into _their_ BankConnector account, so a stock OAuth 2.0 client library works against us with no bespoke SDK.

This is a standards-based flow (RFC 6749 §4.1 + RFC 7636 PKCE, RFC 8252 for native apps), so most of it you already know from any other OAuth provider. The one thing worth reading closely is the scope section below — it decides exactly what your add-on can touch, and it is narrower than an API key.

The authoritative source: RFC 8414 discovery

Point your OAuth client library at:

GET /.well-known/oauth-authorization-server

This RFC 8414 metadata document is generated from what the server actually enforcesscopes_supported and grant_types_supported are read from the same arrays the authorization and token endpoints validate against — so it cannot advertise a capability the server doesn't have. Fetch it and configure your library from it rather than hardcoding the endpoint URLs below; they're included here for readers, not as the source of truth.

The flow

  1. GET /oauth/authorize — your add-on opens this in the system browser (RFC 8252: never an embedded webview) with the standard RFC 6749 §4.1.1 query parameters: response_type=code, client_id, redirect_uri, scope (space-delimited, drawn from the vocabulary below), state, code_challenge, code_challenge_method=S256. PKCE is mandatory — there is no client secret in this flow at all; a public native-app client authenticates itself by proving it holds the code_verifier matching the code_challenge it registered.
  2. The signed-in Admin who owns the account sees a consent screen and approves or declines. On approval the browser is redirected to your registered loopback address (http://127.0.0.1:<any port>/<your path> — exact match; localhost is never accepted) carrying a single-use authorization code (10-minute TTL) and your state.
  3. POST /oauth/token (application/x-www-form-urlencoded) exchanges the code + code_verifier for a token pair:
  4. access_token (bc_oat_…) — 1 hour lifetime, sent as Authorization: Bearer <access_token> against the product API exactly like an API key.
  5. refresh_token (bc_ort_…) — 180 days, and rotates on every use: grant_type=refresh_token returns a new refresh token, and the old one is immediately dead. Replaying an already-rotated refresh token is treated as theft and revokes the entire token family — so a client that loses track of the current refresh token (a crash mid-rotation, a race between two processes sharing one token store) needs the user to re-consent, by design.
  6. POST /oauth/revoke (RFC 7009) revokes a token your add-on no longer needs — on uninstall, for example. It always answers 200, including for an already-revoked, expired, or unknown token (RFC 7009 §2.2's anti-oracle requirement: the endpoint must not leak which tokens exist).

⚠️ /oauth/token and /oauth/revoke do NOT use this API's usual error envelope. Every other endpoint in this product answers an error as { "error": { "code", "message" } } (see Errors & Retries). These two are the standards-mandated exception: they answer the flat RFC 6749 §5.2 shape, { "error": "invalid_grant", "error_description": "…" }, because that is what an off-the-shelf OAuth client library parses. Don't reuse your error.code handling here.

There is deliberately no device-flow (RFC 8628) entry in the machine-readable OpenAPI security scheme — OpenAPI 3.1's flows object has no shape for it. It's not gone; it just isn't expressible in that one document. Ask your BankConnector contact if your integration needs it.

Scopes: what your add-on can actually reach

An install's authority is its grant — the set of scopes the authorizing Admin approved — never the identity of the human who clicked "Allow". The API Reference states this per operation: every endpoint an install can call carries an OAuth2 security option listing the exact scope it needs, right beside the ApiKeyAuth / SessionToken options a first-party integration would use instead. If an operation's reference entry carries no OAuth2 option, no grant reaches it — see the two carve-outs below before assuming that's an oversight.

The full grantable (delegable) vocabulary:

ScopeCovers
payments:submitSubmitting payments (POST /journal/payments and previews).
payments:readReading journal documents / reconciliation data.
statements:readReading account statements and balances.
engine:useThe stateless conversion/validation engine (/convert, /validate, bank + payment-type discovery).
banks:readReading bank connectivity/profile metadata.
webhooks:manageRegistering and managing webhook endpoints.

Request only what your add-on needs — the consent screen shows the Admin exactly this list, by name, and a narrower request is both a better trust signal and less to lose if your stored token ever leaks.

Some actions can never be delegated to any add-on, whatever the authorizing Admin holds: bank connection setup/activation, approval-policy management, approval decisions themselves, and managing other add-ons' grants. These require a live, signed-in human Admin session every time — that's a deliberate security boundary (a compromised or malicious add-on must never be able to redirect where payments go, or weaken the approval control that exists to contain it), not a gap in the scope list.

⚠️ webhooks:manage is in the grantable vocabulary, but the webhook management endpoints are not install-callable yet. Today those routes admit a signed-in Admin or the account's own API key, not an OAuth-authorized install — requesting webhooks:manage will be accepted at consent time, but calls to /webhooks/* with that token will still 403. Provision webhook endpoints with an API key or an Admin session for now; ask your BankConnector contact for the current status if your add-on needs to self-serve this.