# BankConnector OpenAPI Specification — vendored for docs.bankconnector.com
# Source: src/api/openapi.ts, built by scripts/build-docs-site.mjs. Do not edit by hand.
openapi: 3.1.0
info:
title: BankConnector API
version: 0.1.0
description: |
## Quick start
BankConnector converts canonical JSON payments to bank-specific ISO 20022 files and
normalises incoming bank statements into one format. Try the whole flow in the hosted
sandbox at **https://sandbox.bankconnector.com** — no signup, and no real money moves.
### Step 1: Get a sandbox API key
One unauthenticated call provisions a complete, isolated sandbox — your own platform, a
company, an admin user, an API key, and an active demo bank connection:
```http
POST /sandbox/provision
{ "bankKey": "danske-dk" }
→ 201 { "apiKey": "key_…", "companyId": "comp_…", "baseUrl": "https://sandbox.bankconnector.com", … }
```
The raw key is shown ONCE — store it, and send it as the `X-API-Key` header on every
request. **Your key identifies your platform**, so on every data-plane call you pass only
`companyId` — you never send `platformId`.
### Step 2: Submit a payment
```http
POST /journal/payments
X-API-Key: key_…
Idempotency-Key: 8f2c… (unique per payment; mandatory for host-to-host)
{
"companyId": "comp_…", "bankKey": "danske-dk",
"payment": {
"messageId": "MSG-20260619-0001",
"initiatingParty": { "name": "Acme Corp", "organisationId": { "id": "ACME-1", "scheme": "CUST" } },
"payments": [{
"paymentId": "PMT-1", "paymentType": "sepa", "executionDate": "2026-06-20",
"debtor": { "name": "Acme Corp", "country": "DK", "account": { "iban": "DK5000400440116243", "currency": "EUR" }, "agent": { "bic": "NDEADKKK" } },
"transactions": [{ "endToEndId": "E2E-1", "amount": "1000.00", "currency": "EUR",
"creditor": { "name": "Supplier GmbH", "account": { "iban": "DE89370400440532013000" }, "agent": { "bic": "DEUTDEFF" } } }]
}]
}
}
```
The `payment` object IS the canonical instruction (amounts are exact decimal **strings**); on
this route it is WRAPPED under `payment` alongside `companyId` + `bankKey`. The flat 201 response
includes `id`, `journalNo`, `status`, the generated `xml`, and `deliveryMode`. A retry with the
same `Idempotency-Key` + body replays that 201 with an `Idempotency-Replayed: true` header. NOTE the
envelope difference: `POST /validate/{bank}` and `POST /convert/{bank}` take that SAME canonical object
as the raw top-level body (no `payment` wrapper, no companyId). See CANONICAL_INPUT.md for the full shape.
### Step 3: Read results
```http
GET /journal/reconciliation → matched payments + bank confirmations (cleared / rejected / pending)
GET /journal/list?unretrievedOnly=true → poll for documents you have not collected yet
POST /inbound/payment-status → import a pain.002 status report
POST /inbound/statement → import a camt.053 / 052 / 054 statement
```
---
> **Sandbox vs production:** the sandbox at https://sandbox.bankconnector.com is fully isolated —
> the key, data and demo bank are sandbox-only and no real money can move. Going live (real bank
> connections + approvals) is a separate step; the same request shapes apply.
contact:
email: johan@laymanadvisory.com
servers:
-
url: https://sandbox.bankconnector.com
description: 'Sandbox — the default target for every "Try it" call on this site, and where you should do all integration work first. The demo bank channel never moves real money by construction; a real bank connection here is safe from real sends only if your company is provisioned sandboxOnly — confirm that with your operator rather than assuming it from the host alone.'
-
url: 'https://{company}.bankconnector.com'
description: Production — your company's branded subdomain.
variables:
company:
default: your-company
description: 'Your company''s subdomain, from your BankConnector operator.'
tags:
-
name: Discovery
description: Explore what banks and payment types are available
-
name: Conversion
description: Validate and convert canonical payment JSON
-
name: Connections
description: 'Bank connection setup, channel activation, and EBICS onboarding (session-authenticated, Admin only)'
-
name: Journal
description: 'Submit payments + read the journal: the ERP integration surface (X-API-Key)'
-
name: Approvals
description: 'Maker-checker approval policies + the pending-approval queue (session, Admin/Approver)'
-
name: Accounts
description: Bank accounts registry + imported statements (session)
-
name: Webhooks
description: 'Outbound event subscriptions (session, Admin)'
-
name: Auth
description: 'Login, session, and 2FA. Login + demo are PUBLIC; the rest need a session.'
-
name: User Management
description: 'Create, invite, and manage users within a company. Also covers workspace (platform) admin accounts.'
-
name: Platform
description: Tenant dashboard + per-bank test payments
-
name: System
description: Health and version
x-tagGroups:
-
name: Quick Start
tags:
- Auth
- User Management
- Platform
- System
-
name: Banks
tags:
- Discovery
- Connections
-
name: Payments
tags:
- Conversion
- Approvals
-
name: Incoming Data
tags:
- Journal
- Accounts
-
name: Configuration
tags:
- Webhooks
paths:
/sandbox/provision:
post:
tags:
- Getting started
summary: Create a sandbox in one call (no signup)
description: 'Self-serve onboarding — **sandbox only**. With NO authentication, provisions a complete, isolated sandbox: your own platform, a company, an admin user, an **API key**, and an active demo bank connection. Everything created is sandbox-only (no real money can move). Returns the raw API key (shown once) plus a ready-to-run first payment. Your key identifies the platform, so on later requests you pass only `companyId`. Rate-limited per IP; unused sandboxes are removed after 30 days.'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
bankKey:
type: string
description: Demo bank to connect (default `danske-dk`). See GET /profiles.
example: danske-dk
companyName:
type: string
description: Optional name for the created company.
example: Acme Sandbox
label:
type: string
description: Optional label for the API key.
example: my-integration
responses:
'201':
description: Sandbox provisioned
content:
application/json:
schema:
type: object
properties:
apiKey:
type: string
description: Raw API key — shown ONCE. Send as the X-API-Key header.
example: key_1a2b…
companyId:
type: string
example: comp_…
platformId:
type: string
example: plat_…
baseUrl:
type: string
example: https://sandbox.bankconnector.com
bank:
type: object
properties:
key:
type: string
name:
type: string
country:
type: string
firstPayment:
type: object
description: A ready-to-run POST /journal/payments request (headers + body).
'404':
description: Not a deployed sandbox (this endpoint does not exist in production).
'429':
description: Rate limited — too many sandboxes from this IP recently.
/health:
get:
tags:
- System
summary: Liveness check
responses:
'200':
description: Server is running
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: ok
/version:
get:
tags:
- System
summary: API version
responses:
'200':
description: Current version
content:
application/json:
schema:
type: object
properties:
version:
type: string
example: 0.1.0
/ready:
get:
tags:
- System
summary: Readiness check
description: 'Readiness probe for orchestrators (distinct from /health liveness): 200 when the app can serve — the database is reachable AND migrated when one is configured — else 503 so the load balancer keeps traffic away. Public, like /health.'
responses:
'200':
description: Ready
content:
application/json:
schema:
type: object
properties:
ready:
type: boolean
example: true
'503':
description: Not ready (database unreachable or schema not migrated)
content:
application/json:
schema:
type: object
properties:
ready:
type: boolean
example: false
error:
type: string
/fx/rates:
get:
tags:
- System
summary: Indicative FX reference rates
description: 'Global (non-tenant) indicative EUR-base FX rates for converting balances to a common currency. Read-only, kept fresh by a daily job (seeded so it works offline). Shape: `{ base, rates, asOf }`.'
responses:
'200':
description: Cached EUR-base rates
content:
application/json:
schema:
type: object
properties:
base:
type: string
example: EUR
rates:
type: object
additionalProperties:
type: number
asOf:
type: string
nullable: true
required:
- base
- rates
/profiles:
get:
tags:
- Discovery
summary: List all banks with their offered payment types
description: 'Returns every registered bank, its ISO 20022 version, and the payment types available for its country (with full metadata).'
responses:
'200':
description: List of bank profiles
content:
application/json:
schema:
type: object
properties:
profiles:
type: array
items:
$ref: '#/components/schemas/BankProfile'
x-sandbox: true
x-codeSamples:
-
lang: Shell
label: cURL
source: |
curl -X GET https://your-host/profiles \
-H 'X-API-Key: YOUR_KEY'
-
lang: JavaScript
label: Fetch
source: |
const res = await fetch("https://your-host/profiles", {
method: "GET",
headers: {
"X-API-Key": "YOUR_KEY",
},
});
const data = await res.json();
-
lang: Python
label: Python
source: |
import requests
resp = requests.request(
"GET", "https://your-host/profiles",
headers={"X-API-Key": "YOUR_KEY"},
)
resp.raise_for_status()
data = resp.json()
/payment-types:
get:
tags:
- Discovery
summary: Full payment-type catalog
description: 'All payment types in the shared catalog with ISO 20022 encoding, descriptions, and requirements.'
responses:
'200':
description: Full catalog
content:
application/json:
schema:
type: object
properties:
paymentTypes:
type: array
items:
$ref: '#/components/schemas/PaymentTypeEntry'
x-sandbox: true
/connectivity/profiles:
get:
tags:
- Connections
summary: List connectivity profiles (channel info per bank)
description: 'Read-only metadata: which channel each bank uses (sftp / danske-ws / nordea-ca / bankconnect / ebics), its pre-known server/endpoint details, and SFTP wizard config. No auth required.'
responses:
'200':
description: Connectivity profiles
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
type: object
x-sandbox: true
'/platforms/{platformId}/companies':
get:
tags:
- Workspace Admin
summary: List the companies under a platform
description: 'Workspace (platform) admin only. The company list a workspace admin manages: drives the GUI company list + company-switcher (POST /auth/company-context).'
security:
-
SessionToken: []
parameters:
-
name: platformId
in: path
required: true
schema:
type: string
responses:
'200':
description: Companies
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
type: object
properties:
id:
type: string
name:
type: string
createdAt:
type: string
format: date-time
nextCursor:
type: string
nullable: true
'403':
description: Not a workspace admin
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
post:
tags:
- Workspace Admin
summary: Create a company under a platform
description: 'Workspace (platform) admin only. Creates a new company (tenant) under the platform. Requires a **session** (workspace-admin login): not an API key. After creation, use `POST /auth/company-context` to enter the company and make company-scoped calls.'
security:
-
SessionToken: []
parameters:
-
name: platformId
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
maxLength: 200
example: Acme Payments Ltd
description: Display name for the company.
responses:
'201':
description: Company created
content:
application/json:
schema:
type: object
properties:
company:
type: object
properties:
id:
type: string
example: comp_…
name:
type: string
createdAt:
type: string
format: date-time
'400':
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Not a workspace admin
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/auth/company-context:
post:
tags:
- Workspace Admin
summary: Enter a company context (platform admin acts AS a company)
description: Workspace (platform) admin only. Sets the session's active company so the admin acts as an ADMIN of that company. The company must belong to the admin's platform. Cleared with DELETE.
security:
-
SessionToken: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- companyId
properties:
companyId:
type: string
responses:
'200':
description: Context set
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
'403':
description: Not a workspace admin
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Unknown company in your platform
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
delete:
tags:
- Workspace Admin
summary: Exit the company context (back to pure platform-admin scope)
security:
-
SessionToken: []
responses:
'200':
description: Context cleared
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
'403':
description: Not a workspace admin
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/platforms/{platformId}/api-keys':
get:
tags:
- Workspace Admin
summary: 'List a platform''s API keys (metadata only: never the secret)'
description: 'Workspace (platform) admin only. Returns active (non-revoked) keys with id, label, createdAt and lastUsedAt. The raw key is NEVER returned here: it is shown once, at creation.'
security:
-
SessionToken: []
parameters:
-
name: platformId
in: path
required: true
schema:
type: string
responses:
'200':
description: API keys (no secret)
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
type: object
properties:
id:
type: string
label:
type: string
createdAt:
type: string
format: date-time
lastUsedAt:
type: string
format: date-time
nextCursor:
type: string
nullable: true
'403':
description: Not a workspace admin
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
post:
tags:
- Workspace Admin
summary: 'Create an API key: the raw key is returned ONCE'
description: Workspace (platform) admin only. Mints a new platform API key; the raw `key` value appears ONLY in this 201 response and is never stored or retrievable again. Store it securely.
security:
-
SessionToken: []
parameters:
-
name: platformId
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- label
properties:
label:
type: string
description: 'Human name, e.g. "Production ERP".'
responses:
'201':
description: 'Key created: raw key shown once'
content:
application/json:
schema:
type: object
properties:
id:
type: string
key:
type: string
description: The raw API key. Shown ONCE. Never returned again.
label:
type: string
createdAt:
type: string
format: date-time
'400':
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Not a workspace admin
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/platforms/{platformId}/api-keys/{keyId}':
delete:
tags:
- Workspace Admin
summary: Revoke an API key
description: Workspace (platform) admin only. Soft-revokes the key (it stays for audit but stops authenticating immediately).
security:
-
SessionToken: []
parameters:
-
name: platformId
in: path
required: true
schema:
type: string
-
name: keyId
in: path
required: true
schema:
type: string
responses:
'200':
description: Revoked
content:
application/json:
schema:
type: object
'403':
description: Not a workspace admin
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Unknown key for this platform
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/platforms/{platformId}/admins':
get:
tags:
- User Management
summary: List workspace admins for a platform
description: Returns all workspace (platform) admin accounts. Workspace-admin session required.
security:
-
SessionToken: []
parameters:
-
name: platformId
in: path
required: true
schema:
type: string
responses:
'200':
description: Workspace admins
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
type: object
properties:
id:
type: string
email:
type: string
name:
type: string
nextCursor:
type: string
nullable: true
'403':
description: Not a workspace admin
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
post:
tags:
- User Management
summary: Create a workspace admin
description: 'Creates a new workspace (platform) admin account with a password. **Bootstrap note:** when the server is running in open/unauthenticated mode (no `BANKCONNECTOR_API_KEYS` set, e.g. first-time setup), this endpoint is accessible without a session: use it to create the very first admin. Once auth is enabled, an existing workspace-admin session is required.'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- email
- name
- password
properties:
email:
type: string
format: email
example: admin@acme.com
name:
type: string
example: Alice Admin
password:
type: string
description: Initial password. Minimum 8 characters.
responses:
'201':
description: Admin created
content:
application/json:
schema:
type: object
properties:
id:
type: string
email:
type: string
name:
type: string
'400':
description: Invalid request or duplicate email
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Not a workspace admin (when auth is enabled)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/users:
get:
tags:
- User Management
summary: List users in a company
description: 'Returns all users in the company (name, email, roles, status). Readable by any authenticated company member.'
security:
-
SessionToken: []
parameters:
-
name: platformId
in: query
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Company users
content:
application/json:
schema:
type: object
properties:
users:
type: array
items:
type: object
properties:
id:
type: string
email:
type: string
name:
type: string
roles:
type: array
items:
type: string
enum:
- admin
- approver
- viewer
status:
type: string
enum:
- active
- invited
'401':
description: Sign in required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
post:
tags:
- User Management
summary: Create or invite a company user
description: |
**Company admin** only. Two modes:
- **With `password`:** creates the user immediately (active). Use for programmatic provisioning.
- **Without `password`:** sends an invite email. The user receives a link to `/auth/set-password` where they set their own password.
The user must be given at least one role (`admin`, `approver`, and/or `viewer`). Users who will be assigned as payment approvers need the `approver` role.
security:
-
SessionToken: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- email
- name
- roles
properties:
platformId:
type: string
companyId:
type: string
email:
type: string
format: email
example: approver@acme.com
name:
type: string
example: Bob Approver
roles:
type: array
items:
type: string
enum:
- admin
- approver
- viewer
minItems: 1
example:
- approver
password:
type: string
description: 'If omitted, an invite email is sent and the user sets their own password.'
responses:
'201':
description: User created or invite sent
content:
application/json:
schema:
type: object
properties:
id:
type: string
email:
type: string
name:
type: string
roles:
type: array
items:
type: string
invited:
type: boolean
description: true when an invite email was sent (no password provided)
'400':
description: 'Invalid request, duplicate email, or no valid roles'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Company admin required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/auth/set-password:
post:
tags:
- User Management
summary: 'Complete an invite: set password from email token (PUBLIC)'
description: PUBLIC. Called when an invited user clicks their invite link. Validates the one-time token and sets the user's password. Any existing sessions for the user are revoked. The token comes from the invite email.
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- token
- password
properties:
token:
type: string
description: The one-time invite token from the email link.
password:
type: string
description: The user's chosen password.
responses:
'200':
description: Password set
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
email:
type: string
'400':
description: Invalid or expired token
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: 'Too many attempts: IP rate-limited'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/connections:
get:
tags:
- Connections
summary: List bank connections for a company
security:
-
SessionToken: []
parameters:
-
name: platformId
in: query
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Connection list
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
$ref: '#/components/schemas/BankConnection'
'401':
description: Sign in required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
post:
tags:
- Connections
summary: 'Create (or find existing) bank connection: starts the setup wizard'
description: 'Creates a bank connection for the given company, or returns the existing one if already set up. **Platform-admin note:** if you are authenticated as a workspace (platform) admin, you must enter a company context first via `POST /auth/company-context` before calling this endpoint. Without it you will receive: *"select a company context first."*'
security:
-
SessionToken: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- bankKey
properties:
platformId:
type: string
companyId:
type: string
bankKey:
$ref: '#/components/schemas/BankKey'
environment:
type: string
enum:
- test
- production
responses:
'201':
description: Connection created
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'400':
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Sign in required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Admin required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/connections/{id}':
get:
tags:
- Connections
summary: Get a single connection + readiness
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
-
name: platformId
in: query
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Connection + readiness
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/connections/{id}/signing-cert':
put:
tags:
- Connections
summary: Pin the bank's published signing certificate
description: 'Stores the bank''s PUBLISHED X.509 signing certificate (PEM or base64-DER) on the connection so inbound Web Services signatures are verified against it (fail-closed on any mismatch). Admin only. Validates the cert parses (400 on bad input). Returns the cert subject + expiry so the operator can confirm they pinned the right cert; the raw PEM is never returned. GET /connections/{id} then surfaces signingCertSubject / signingCertExpiry.'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- cert
properties:
platformId:
type: string
companyId:
type: string
cert:
type: string
description: The bank's published signing certificate — PEM (-----BEGIN CERTIFICATE-----) or base64-DER.
responses:
'200':
description: Pinned
content:
application/json:
schema:
type: object
properties:
connectionId:
type: string
certSubject:
type: string
certExpiry:
type: string
format: date-time
'400':
description: Bad certificate or non-WS connection
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Sign in required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Admin required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/connections/{id}/activate-danske':
post:
tags:
- Connections
summary: Activate Danske EDI Web Services (automated cert enrolment)
description: 'Calls the Danske bxd.fi activation endpoint with the one-time PIN, self-issues the signing + encryption certificates, and marks the connection active. Admin only.'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- userId
- pin
properties:
platformId:
type: string
companyId:
type: string
userId:
type: string
description: Agreement number (User ID) from Danske.
pin:
type: string
description: One-time transfer key from Danske.
responses:
'200':
description: Activated
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'400':
description: 'Activation failed (wrong PIN, network, etc.)'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Sign in required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Admin required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/connections/{id}/activate-nordea':
post:
tags:
- Connections
summary: Activate Nordea Corporate Access (HMAC cert enrolment via SMS code)
description: 'Generates a Nordea-format signing CSR, sends the HMAC-signed enrolment request to Nordea''s Corporate Access endpoint using the SMS activation code, and stores the issued certificate. Admin only.'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- holderName
- signerId
- country
- activationCode
properties:
platformId:
type: string
companyId:
type: string
holderName:
type: string
description: Certificate-holder name (CN) as printed in the Nordea agreement.
signerId:
type: string
description: Signer ID from the agreement.
senderId:
type: string
country:
type: string
minLength: 2
maxLength: 2
activationCode:
type: string
description: 10-digit SMS activation code from Nordea.
responses:
'200':
description: Activated
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'400':
description: Activation failed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Sign in required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Admin required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/connections/{id}/activate-bankconnect':
post:
tags:
- Connections
summary: 'Activate Bank Connect (DK gateway: automated cert enrolment)'
description: 'Enrols the signing certificate with the Bank Connect data central (BD/BEC/SDC), stores the issued certificate and bank public key, and marks the connection active. Data central and country are pre-filled from the bank profile. Admin only.'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- organisationId
- functionId
- activationCode
properties:
platformId:
type: string
companyId:
type: string
organisationId:
type: string
description: Bank registration number.
functionId:
type: string
description: Per-agreement routing code from the bank.
activationCode:
type: string
description: One-time activation code from the bank.
responses:
'200':
description: Activated
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'400':
description: Activation failed or bank has no Bank Connect data central
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Sign in required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Admin required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Connection not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/connections/{id}/generate-pgp':
post:
tags:
- Connections
summary: Generate our PGP key pair (SFTP channel)
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
properties:
platformId:
type: string
companyId:
type: string
responses:
'200':
description: PGP key pair generated
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'401':
description: Sign in required
'403':
description: Admin required
'/connections/{id}/bank-key':
post:
tags:
- Connections
summary: Save the bank's PGP public key (SFTP channel)
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- armoredKey
properties:
platformId:
type: string
companyId:
type: string
armoredKey:
type: string
description: ASCII-armored PGP public key block.
responses:
'200':
description: Bank key saved
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'401':
description: Sign in required
'403':
description: Admin required
'/connections/{id}/server-info':
post:
tags:
- Connections
summary: 'Save SFTP server info (host, port, paths, fingerprint)'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- host
properties:
platformId:
type: string
companyId:
type: string
host:
type: string
port:
type: integer
default: 22
uploadPath:
type: string
downloadPath:
type: string
hostFingerprint:
type: string
responses:
'200':
description: Server info saved
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'401':
description: Sign in required
'403':
description: Admin required
'/connections/{id}/sftp-user':
post:
tags:
- Connections
summary: Set SFTP username and generate SSH key pair (single combined call)
description: 'Sets the SFTP username AND generates the SSH key pair in one call: you cannot pre-generate the key before you have the username. The `username` must be obtained from the bank first; only then can you call this endpoint. The response includes the connection + readiness state, from which you can retrieve the generated SSH public key to send to the bank for whitelisting.'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- username
properties:
platformId:
type: string
companyId:
type: string
username:
type: string
description: 'SFTP username assigned by the bank. Required: the SSH key generation and username registration are a single atomic operation.'
responses:
'200':
description: Username set + SSH key generated
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'401':
description: Sign in required
'403':
description: Admin required
'/connections/{id}/approvers':
post:
tags:
- Connections
summary: Assign approver users to a connection
description: 'Assigns which users may approve payments on this connection. **Approvers and approval policy are separate:** the policy (under `POST /approvals/policies`) defines the rule (how many approvers, any amount thresholds); this endpoint assigns the eligible users per connection. Both must be in place for a connection to reach go-live. Note: users must first exist in the company: create them with `POST /users` before assigning them as approvers.'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- userIds
properties:
platformId:
type: string
companyId:
type: string
userIds:
type: array
items:
type: string
description: User IDs of company members with the Approver role. Must already exist in the company.
responses:
'200':
description: Approvers assigned
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'401':
description: Sign in required
'403':
description: Admin required
'422':
description: One or more user IDs are not valid approvers in this company
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/connections/{id}/activate':
post:
tags:
- Connections
summary: Mark SFTP connection active (after the bank has whitelisted the SSH key)
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
properties:
platformId:
type: string
companyId:
type: string
responses:
'200':
description: Connection activated
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'401':
description: Sign in required
'403':
description: Admin required
'/connections/{id}/intro-email-sent':
post:
tags:
- Connections
summary: Mark intro email as sent (tracks wizard progress)
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
properties:
platformId:
type: string
companyId:
type: string
responses:
'200':
description: Updated
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'401':
description: Sign in required
'403':
description: Admin required
'/connections/{id}/ws-environment':
post:
tags:
- Connections
summary: Toggle a Web Services connection between TEST and PRODUCTION
description: Requires an already-activated connection (certificates issued); does not re-run PKI.
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- environment
properties:
platformId:
type: string
companyId:
type: string
environment:
type: string
enum:
- TEST
- PRODUCTION
responses:
'200':
description: Environment updated
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'401':
description: Sign in required
'403':
description: Admin required
'/connections/{id}/covered-banks':
post:
tags:
- Connections
summary: Set which member banks a shared connection covers
description: For a shared (group) connection. The store clamps the set to the bank's group and always includes the connection's own bank.
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- coveredBankKeys
properties:
platformId:
type: string
companyId:
type: string
coveredBankKeys:
type: array
items:
type: string
description: Bank keys this connection serves (clamped to the bank's group).
responses:
'200':
description: Covered banks updated
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'401':
description: Sign in required
'403':
description: Admin required
'/connections/{id}/ebics-params':
post:
tags:
- Connections
summary: 'Save EBICS connection parameters (Host ID, Partner ID, User ID, …)'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- url
- hostId
- partnerId
- userId
properties:
platformId:
type: string
companyId:
type: string
url:
type: string
format: uri
description: EBICS server URL.
hostId:
type: string
description: Bank-assigned Host ID.
partnerId:
type: string
description: Customer ID (PartnerID) from the bank.
userId:
type: string
description: Subscriber ID (UserID) from the bank.
protocolVersion:
type: string
example: H005
signatureVersion:
type: string
example: A006
subscriberMode:
type: string
enum:
- single
- multi
responses:
'200':
description: EBICS params saved
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'401':
description: Sign in required
'403':
description: Admin required
'/connections/{id}/ebics-generate-keys':
post:
tags:
- Connections
summary: 'Generate EBICS subscriber keys (authentication, encryption, signature)'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
properties:
platformId:
type: string
companyId:
type: string
responses:
'200':
description: Keys generated
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionWithReadiness'
'401':
description: Sign in required
'403':
description: Admin required
'/connections/{id}/intro-email':
get:
tags:
- Connections
summary: Render the bank intro email (draft to send to the bank contact)
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
-
name: platformId
in: query
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Rendered email (subject + body)
content:
application/json:
schema:
type: object
'404':
description: Not found
'/connections/{id}/ebics-letter':
get:
tags:
- Connections
summary: 'Download the EBICS initialisation letter (PDF, base64-encoded)'
description: 'Returns the signed INI/HIA initialisation letter as a base64-encoded PDF. Print, sign, and send to the bank to complete EBICS subscriber activation.'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
-
name: platformId
in: query
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: PDF as base64
content:
application/json:
schema:
type: object
properties:
pdfBase64:
type: string
filename:
type: string
'404':
description: Not found or keys not yet generated
'/banks/{bankKey}/setup-info':
get:
tags:
- Connections
summary: Per-bank setup descriptor for the wizard UI
description: 'Returns the channel kind, requirements text, field definitions, pre-known server/endpoint details, and SFTP wizard screens for a bank. Used by the setup wizard to render the correct flow.'
parameters:
-
name: bankKey
in: path
required: true
schema:
$ref: '#/components/schemas/BankKey'
responses:
'200':
description: Setup descriptor
content:
application/json:
schema:
type: object
'404':
description: Unknown bank
x-sandbox: true
'/banks/{bankKey}/settings':
get:
tags:
- Connections
summary: Get per-company settings for a specific bank
security:
-
SessionToken: []
parameters:
-
name: bankKey
in: path
required: true
schema:
$ref: '#/components/schemas/BankKey'
-
name: platformId
in: query
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Bank settings (null if none saved)
content:
application/json:
schema:
type: object
post:
tags:
- Connections
summary: Update per-company settings for a specific bank
description: 'Payment-affecting fields (agreementId, chargeBearer, executionDateOffsetDays, approvalPolicyId) require Admin. The `selected` pin is open to any member.'
security:
-
SessionToken: []
parameters:
-
name: bankKey
in: path
required: true
schema:
$ref: '#/components/schemas/BankKey'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
platformId:
type: string
companyId:
type: string
agreementId:
type: string
chargeBearer:
type: string
enum:
- SLEV
- SHAR
- CRED
- DEBT
executionDateOffsetDays:
type: integer
approvalPolicyId:
type: string
selected:
type: boolean
responses:
'200':
description: Settings saved
content:
application/json:
schema:
type: object
'401':
description: Sign in required
'403':
description: Admin required for payment-affecting fields
/bank-settings:
get:
tags:
- Connections
summary: List all per-company bank settings in one call
security:
-
SessionToken: []
parameters:
-
name: platformId
in: query
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: All saved bank settings for this company
content:
application/json:
schema:
type: object
properties:
settings:
type: array
items:
type: object
/bank-requests:
post:
tags:
- Platform
summary: Request a bank we don't support yet
description: Any signed-in user (or anonymous) can submit a BIC or bank name. The request is queued for the development team.
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- query
properties:
query:
type: string
description: BIC code or bank name.
email:
type: string
format: email
responses:
'201':
description: Request recorded
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
request:
type: object
properties:
id:
type: string
query:
type: string
createdAt:
type: string
format: date-time
'400':
description: Empty query
get:
tags:
- Platform
summary: List all bank requests (Admin only)
security:
-
SessionToken: []
responses:
'200':
description: All requests
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
type: object
'401':
description: Sign in required
'403':
description: Admin required
'/banks/{bankKey}/test-scenarios':
get:
tags:
- Platform
summary: Available one-click Test Payment scenarios for a bank
description: 'Lists the canned test-payment scenarios this bank can send (domestic, sepa, international, urgent), tailored to its country and offered payment types: each with its label, amount, currency and the chosen payment type.'
parameters:
-
name: bankKey
in: path
required: true
schema:
type: string
responses:
'200':
description: Offered scenarios
content:
application/json:
schema:
type: object
properties:
scenarios:
type: array
items:
type: object
x-sandbox: true
'/banks/{bankKey}/test-payment':
post:
tags:
- Platform
summary: Send a one-click test payment
description: 'Builds a schema-valid canonical payment for the chosen scenario from the debtor account supplied, then runs it through the normal convert → approval → deliver path over the chosen environment (defaults to the test channel). Requires a signed-in session.'
parameters:
-
name: bankKey
in: path
required: true
schema:
$ref: '#/components/schemas/BankKey'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- scenario
- account
properties:
platformId:
type: string
companyId:
type: string
scenario:
type: string
enum:
- domestic
- sepa
- international
- urgent
environment:
type: string
enum:
- test
- production
account:
type: object
properties:
name:
type: string
iban:
type: string
other:
type: object
properties:
id:
type: string
scheme:
type: string
currency:
type: string
country:
type: string
responses:
'201':
description: Test payment journaled
content:
application/json:
schema:
type: object
'400':
description: Invalid request
'401':
description: Sign in required
'/banks/{bankKey}/payment-types':
get:
tags:
- Discovery
summary: Payment types offered by a specific bank
parameters:
-
name: bankKey
in: path
required: true
schema:
$ref: '#/components/schemas/BankKey'
example: nordea-dk
responses:
'200':
description: Bank key/name + offered payment types with full metadata
content:
application/json:
schema:
type: object
properties:
bankKey:
type: string
description: 'The bank''s registry key (consistent with /banks/{bankKey}/status).'
bank:
type: string
deprecated: true
description: Deprecated alias of bankKey — kept one release.
bankName:
type: string
countryCode:
type: string
paymentTypes:
type: array
items:
type: object
'404':
description: Unknown bank
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
x-sandbox: true
'/validate/{bankKey}':
post:
tags:
- Conversion
summary: Validate a payment instruction (no conversion)
description: 'Runs all three validation levels (generic → bank → payment-type) and returns every issue at once. Always returns HTTP 200: check `valid` in the response body. Use this to check a payment before sending it.'
parameters:
-
name: bankKey
in: path
required: true
schema:
$ref: '#/components/schemas/BankKey'
example: nordea-dk
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentInstruction'
responses:
'200':
description: 'Validation outcome (valid or not: check `valid`)'
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationOutcome'
'400':
description: Malformed JSON body
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Unknown bank key
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
x-sandbox: true
x-codeSamples:
-
lang: Shell
label: cURL
source: |
curl -X POST https://your-host/validate/nordea-dk \
-H 'X-API-Key: YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]}'
-
lang: JavaScript
label: Fetch
source: |
const res = await fetch("https://your-host/validate/nordea-dk", {
method: "POST",
headers: {
"X-API-Key": "YOUR_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]}),
});
const data = await res.json();
-
lang: Python
label: Python
source: |
import requests
resp = requests.request(
"POST", "https://your-host/validate/nordea-dk",
headers={"X-API-Key": "YOUR_KEY"},
json={"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]},
)
resp.raise_for_status()
data = resp.json()
'/convert/{bankKey}':
post:
tags:
- Conversion
summary: Validate then convert to bank-specific pain.001 XML
description: 'Validates the input (all three levels) then converts it to the bank''s specific ISO 20022 `pain.001` format. Returns XML on success. Add `?validate=true` to additionally validate the generated XML against the official ISO 20022 XSD. This is the STATELESS engine — it does NOT apply per-company bank settings (charge bearer, execution-date offset). Pass `?painVersion=` to choose the output version here; per-company settings are applied only on the delivery path, `POST /journal/payments`. Use that route to see a payment exactly as it will be submitted.'
parameters:
-
name: bankKey
in: path
required: true
schema:
$ref: '#/components/schemas/BankKey'
example: nordea-dk
-
name: validate
in: query
required: false
schema:
type: boolean
description: 'If true, XSD-validate the generated XML before returning it.'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentInstruction'
responses:
'200':
description: Bank-specific pain.001 XML
content:
application/xml:
schema:
type: string
'400':
description: Malformed JSON body
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Unknown bank key
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
description: 'Validation failed: returns all issues at once'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
x-sandbox: true
x-codeSamples:
-
lang: Shell
label: cURL
source: |
curl -X POST https://your-host/convert/nordea-dk \
-H 'X-API-Key: YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]}'
-
lang: JavaScript
label: Fetch
source: |
const res = await fetch("https://your-host/convert/nordea-dk", {
method: "POST",
headers: {
"X-API-Key": "YOUR_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]}),
});
const data = await res.text(); // pain.001 XML
-
lang: Python
label: Python
source: |
import requests
resp = requests.request(
"POST", "https://your-host/convert/nordea-dk",
headers={"X-API-Key": "YOUR_KEY"},
json={"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]},
)
resp.raise_for_status()
data = resp.text # pain.001 XML
'/convert-async/{bankKey}':
post:
tags:
- Conversion
summary: Enqueue a background conversion (returns a job id)
description: 'Non-blocking sibling of POST /convert/{bankKey}: validates the input, enqueues a background conversion, and returns 202 with a `jobId` immediately. Poll GET /convert-jobs/{id} for the result. `?validate=true` is NOT supported here (the async worker can''t run the XSD) — use the synchronous /convert/{bankKey}?validate=true instead.'
parameters:
-
name: bankKey
in: path
required: true
schema:
$ref: '#/components/schemas/BankKey'
example: nordea-dk
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentInstruction'
responses:
'202':
description: Conversion enqueued
content:
application/json:
schema:
type: object
properties:
jobId:
type: string
status:
type: string
bankKey:
type: string
autoPaymentType:
type: string
required:
- jobId
- status
- bankKey
'400':
description: 'Malformed JSON body, ?validate=true (unsupported on the async path), or unsupported painVersion'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Unknown bank key
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
description: 'Validation failed: returns all issues at once'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/convert-jobs/{id}':
get:
tags:
- Conversion
summary: Poll a background conversion job
description: 'Returns the status of a job created by POST /convert-async/{bankKey}. While pending/running the body carries the job status; once succeeded it returns the converted pain.001 XML. A job is readable only by the tenant that created it (others get 404 — never a cross-tenant leak).'
parameters:
-
name: id
in: path
required: true
schema:
type: string
example: job_abc123
responses:
'200':
description: 'Job status, or the converted XML once the job has succeeded'
content:
application/json:
schema:
type: object
application/xml:
schema:
type: string
'404':
description: Conversion job not found (or not owned by the caller)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/journal/payments:
post:
tags:
- Journal
summary: 'Submit a payment: convert + journal (+ deliver if host-to-host)'
description: 'The primary ERP integration call. Validates + converts the canonical payment for `bankKey`, records it in the journal, and routes it through approval + delivery. Pass only `companyId` (+ `bankKey` + `payment`) — **platformId is inferred from your API key; do not send it.** **Idempotency-Key is MANDATORY for host-to-host** (a missing key returns 400 `idempotency_key_required`, unless the `payment.messageId` is supplied and used as the fallback dedupe key). Send a unique key per logical payment so a network-timeout retry can never double-submit. A retry with the SAME key + SAME body replays the original **201** response and carries the header `Idempotency-Replayed: true`; the same key with a DIFFERENT body → 409. Requires authentication (X-API-Key for ERP integrations).'
security:
-
ApiKeyAuth: []
-
SessionToken: []
parameters:
-
name: Idempotency-Key
in: header
required: false
schema:
type: string
minLength: 8
maxLength: 255
description: 'Unique per company per logical payment. MANDATORY for host-to-host (else 400 `idempotency_key_required`, unless `payment.messageId` is provided). Same key + same body replays the original 201 (with `Idempotency-Replayed: true`); same key + different body → 409.'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/JournalPaymentRequest'
responses:
'201':
description: 'Payment recorded + journaled. A replay of a prior idempotent submit ALSO returns 201, additionally carrying the response header `Idempotency-Replayed: true`.'
content:
application/json:
schema:
$ref: '#/components/schemas/JournalDocument'
example:
id: doc_9f3a
journalNo: OUT-000123
platformId: plat_123
companyId: comp_123
bankKey: nordea-dk
environment: production
direction: outbound
type: pain.001
status: converted
summary: SEPA — 1 payment
transactionCount: 1
totals:
-
currency: EUR
amount: '1000.00'
xml: …
deliveryMode: host-to-host
createdAt: 2026-06-20T10:31:00Z
delivery:
status: queued
'400':
description: '`idempotency_key_required` — no Idempotency-Key (header or body) and no `payment.messageId` on a host-to-host submit'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Scope mismatch (cross-tenant)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'409':
description: 'Idempotency conflict (same key, different body) or an in-flight retry'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
description: 'Validation failed: all issues at once'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: 'Rate limited (production): see Retry-After'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
x-codeSamples:
-
lang: Shell
label: cURL
source: |
curl -X POST https://your-host/journal/payments \
-H 'X-API-Key: YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{"companyId":"YOUR_COMPANY_ID","bankKey":"nordea-dk","payment":{"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]}}'
-
lang: JavaScript
label: Fetch
source: |
const res = await fetch("https://your-host/journal/payments", {
method: "POST",
headers: {
"X-API-Key": "YOUR_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({"companyId":"YOUR_COMPANY_ID","bankKey":"nordea-dk","payment":{"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]}}),
});
const data = await res.json();
-
lang: Python
label: Python
source: |
import requests
resp = requests.request(
"POST", "https://your-host/journal/payments",
headers={"X-API-Key": "YOUR_KEY"},
json={"companyId":"YOUR_COMPANY_ID","bankKey":"nordea-dk","payment":{"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]}},
)
resp.raise_for_status()
data = resp.json()
/journal/payments/preview:
post:
tags:
- Journal
summary: 'Preview a payment: convert + validate, return XML (no journal, no send)'
description: 'Read-only. Runs the same settings-fill → validate → convert (+ XSD) pipeline as `POST /journal/payments` and returns the generated bank file, but does NOT journal, approve, or dispatch — and needs no live host-to-host connection. Use it to inspect the exact bank file a payment would produce before submitting it. Requires authentication.'
security:
-
ApiKeyAuth: []
-
SessionToken: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/JournalPaymentRequest'
responses:
'200':
description: Preview generated
content:
application/json:
schema:
type: object
properties:
xml:
type: string
painVersion:
type: string
preview:
type: boolean
'400':
description: Missing fields
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
description: Validation / XSD failed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/journal/ingest:
post:
tags:
- Journal
summary: 'Ingest a bank file (camt.053/054, pain.002, MT940) → normalised JSON'
description: Upload a raw bank statement/status file; it is parsed to the one normalised shape and journaled. Requires authentication.
security:
-
ApiKeyAuth: []
-
SessionToken: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
companyId:
type: string
description: Your company id. platformId is inferred from your API key — do not send it.
bankKey:
type: string
content:
type: string
description: Raw file contents
required:
- companyId
- content
responses:
'201':
description: File ingested + normalised
content:
application/json:
schema:
$ref: '#/components/schemas/JournalDocument'
example:
id: doc_a1b2
journalNo: IN-000042
companyId: comp_123
bankKey: nordea-dk
environment: production
direction: inbound
type: camt.053
status: imported
summary: Statement — 3 entries
transactionCount: 3
totals:
-
currency: EUR
amount: '4200.00'
createdAt: 2026-06-20T08:15:00Z
'401':
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
description: Unrecognised / unparseable file
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/journal/documents:
get:
tags:
- Journal
summary: List journal document summaries (newest first)
description: 'Tenant-scoped list of payments + statements (metadata only: no decrypted payload). Filter by direction/type.'
security:
-
ApiKeyAuth: []
-
SessionToken: []
parameters:
-
name: companyId
in: query
required: true
schema:
type: string
-
name: direction
in: query
required: false
schema:
type: string
enum:
- inbound
- outbound
-
name: type
in: query
required: false
schema:
type: string
example: camt.053
-
name: limit
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 200
description: Page size (enables keyset pagination via nextCursor).
-
name: cursor
in: query
required: false
schema:
type: string
description: Opaque cursor from a prior page's `nextCursor`. (`before` is accepted as a deprecated alias.)
responses:
'200':
description: Document summaries
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
$ref: '#/components/schemas/JournalDocument'
nextCursor:
type: string
nullable: true
'401':
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/journal/export:
post:
tags:
- Journal
summary: Export full document payloads by journal number (admin / API key)
description: 'Bulk extraction of full pain.001/camt payloads (account numbers + amounts), optionally filtered within a statement. Admin-only for human sessions; the ERP API key is the intended consumer.'
security:
-
ApiKeyAuth: []
-
SessionToken: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
companyId:
type: string
description: Your company id. platformId is inferred from your API key — do not send it.
journalNos:
type: array
items:
type: string
required:
- companyId
- journalNos
responses:
'200':
description: Exported documents + any notFound journal numbers
content:
application/json:
schema:
type: object
properties:
exported:
type: array
items:
type: object
notFound:
type: array
items:
type: string
'401':
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Admin required (human session)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/accounts:
get:
tags:
- Accounts
summary: List bank accounts for a company (balances masked)
security:
-
SessionToken: []
parameters:
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Accounts with latest balances
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
type: object
'401':
description: Sign in required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/accounts/statements:
get:
tags:
- Accounts
summary: List imported camt.053 statements for an account
description: Per-account statement history (the parsed NormalisedStatement summaries) used by the Statements screen.
security:
-
SessionToken: []
parameters:
-
name: companyId
in: query
required: true
schema:
type: string
-
name: account
in: query
required: false
schema:
type: string
description: IBAN or account id to filter to.
responses:
'200':
description: Statements + any detected sequence gaps
content:
application/json:
schema:
type: object
properties:
account:
type: string
nullable: true
statements:
type: array
items:
type: object
gaps:
type: array
items:
type: object
description: Possible missing statements detected by the continuity check.
'401':
description: Sign in required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/approvals/pending:
get:
tags:
- Approvals
summary: List payments awaiting approval
security:
-
SessionToken: []
parameters:
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Pending approval requests
content:
application/json:
schema:
type: object
properties:
requests:
type: array
items:
type: object
'401':
description: Sign in required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/approvals/{id}/approve':
post:
tags:
- Approvals
summary: Approve a pending payment (maker ≠ checker enforced)
description: An approver other than the maker approves the request; once the policy's required approvals are met the payment proceeds to delivery. May require a 2FA code/backup code if step-up is configured.
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
properties:
platformId:
type: string
companyId:
type: string
reason:
type: string
code:
type: string
description: 2FA TOTP code (if step-up required).
backupCode:
type: string
responses:
'200':
description: Approval recorded (and payment dispatched if fully approved)
content:
application/json:
schema:
type: object
'401':
description: Sign in required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Maker cannot approve own payment / not an approver
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/approvals/{id}/reject':
post:
tags:
- Approvals
summary: Reject a pending payment
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
properties:
platformId:
type: string
companyId:
type: string
reason:
type: string
responses:
'200':
description: Rejection recorded
content:
application/json:
schema:
type: object
'401':
description: Sign in required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/approvals/policies:
get:
tags:
- Approvals
summary: List approval policies for a company
security:
-
SessionToken: []
parameters:
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Policies
content:
application/json:
schema:
type: object
properties:
policies:
type: array
items:
type: object
'401':
description: Sign in required
post:
tags:
- Approvals
summary: Create an approval policy (Admin)
description: Defines required approver count + amount thresholds. Admin only.
security:
-
SessionToken: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- name
properties:
platformId:
type: string
companyId:
type: string
name:
type: string
requiredApprovals:
type: integer
minimum: 1
thresholdAmount:
type: string
currency:
type: string
responses:
'201':
description: Policy created
content:
application/json:
schema:
type: object
'401':
description: Sign in required
'403':
description: Admin required
'/approvals/policies/{id}':
post:
tags:
- Approvals
summary: Edit a DRAFT approval policy (Admin)
description: 'Edit a policy''s name / required approver count / thresholds. Works only on a DRAFT version — an Active version is immutable (409); change it via /approvals/policies/{id}/propose then co-sign.'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
properties:
platformId:
type: string
companyId:
type: string
name:
type: string
requiredApprovals:
type: integer
minimum: 1
thresholdAmount:
type: string
currency:
type: string
responses:
'200':
description: Updated draft policy
content:
application/json:
schema:
type: object
properties:
policy:
type: object
'401':
description: Sign in required
'403':
description: Admin required
'409':
description: Version is not a draft / conflict
'/approvals/policies/{id}/lock':
post:
tags:
- Approvals
summary: Activate (lock) a draft approval policy (Admin)
description: Locks a DRAFT version → Active and immutable. Runs the quorum check (a dual policy needs ≥2 named approvers); fails 409 if it can't be met. An Active version is changed only via propose + co-sign.
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
properties:
platformId:
type: string
companyId:
type: string
responses:
'200':
description: Policy activated
content:
application/json:
schema:
type: object
properties:
policy:
type: object
'401':
description: Sign in required
'403':
description: Admin required
'409':
description: Quorum not met / conflict
'/approvals/policies/{id}/propose':
post:
tags:
- Approvals
summary: Propose a change to an Active policy (Admin)
description: 'Clones the Active version to an editable draft v(n+1). Edit the draft (POST /approvals/policies/{draftId}), then submit it for activation via `POST /approvals/changes/{changeId}/submit` and have a second Admin co-sign via `POST /approvals/changes/{changeId}/cosign`. The old version is retained forever once superseded.'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
properties:
platformId:
type: string
companyId:
type: string
responses:
'201':
description: Draft version created
content:
application/json:
schema:
type: object
properties:
policy:
type: object
'401':
description: Sign in required
'403':
description: Admin required
'409':
description: Base not Active / change already in progress
/approvals/changes:
get:
tags:
- Approvals
summary: List approval-policy change requests (Admin)
description: The dual-co-sign change requests for the company. `?status=pending` (default) | activated | cancelled.
security:
-
SessionToken: []
parameters:
-
name: status
in: query
required: false
schema:
type: string
enum:
- pending
- activated
- cancelled
responses:
'200':
description: Change requests
content:
application/json:
schema:
type: object
properties:
changes:
type: array
items:
type: object
'401':
description: Sign in required
'403':
description: Admin required
'/approvals/changes/{changeId}/{action}':
post:
tags:
- Approvals
summary: Submit / co-sign / cancel a policy change (Admin)
description: 'action=submit — the proposer submits the draft for activation (counts as the FIRST of 2 co-signs). action=cosign — a SECOND, distinct Admin co-signs: the new version goes Active, the base is Superseded, and attached banks re-point to the new version. action=cancel — any Admin cancels a pending change (the draft is discarded; the base stays Active). A 2nd co-sign by the same Admin is refused (409).'
security:
-
SessionToken: []
parameters:
-
name: changeId
in: path
required: true
schema:
type: string
-
name: action
in: path
required: true
schema:
type: string
enum:
- submit
- cosign
- cancel
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
properties:
platformId:
type: string
companyId:
type: string
responses:
'200':
description: Change updated
content:
application/json:
schema:
type: object
properties:
change:
type: object
policy:
type: object
supersededId:
type: string
affectedBanks:
type: array
items:
type: string
'401':
description: Sign in required
'403':
description: Admin required
'409':
description: Same-admin co-sign / conflict
/journal/list:
get:
tags:
- Journal
summary: 'Paginated journal feed (cursor): the ERP read + collect surface'
description: 'Tenant-scoped, newest-first list of journal documents with a stable opaque `cursor` for forward pagination. Metadata only (no decrypted payload). The flagship ERP polling pattern: pass `unretrievedOnly=true` to fetch only documents you have not collected yet, then mark them collected via POST /journal/export. `total` counts all documents; `unretrieved` counts how many are still uncollected. Each item carries `collected` (boolean) and `collectedAt` so a poller can track exactly what it has pulled. platformId is inferred from your API key — pass only `companyId`.'
security:
-
ApiKeyAuth: []
-
SessionToken: []
parameters:
-
name: companyId
in: query
required: true
schema:
type: string
description: Your company id. (platformId is inferred from your API key — do not send it.)
-
name: cursor
in: query
required: false
schema:
type: string
description: Opaque cursor from a prior page's `nextCursor`.
-
name: limit
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 1000
default: 500
description: 'Page size — default 500, max 1000 (larger than the interactive /journal/documents feed, for bulk ERP pulls).'
-
name: direction
in: query
required: false
schema:
type: string
enum:
- inbound
- outbound
-
name: type
in: query
required: false
schema:
type: string
example: camt.053
-
name: unretrievedOnly
in: query
required: false
schema:
type: boolean
default: false
description: 'When `true`, returns ONLY documents not yet collected (the standard poll-then-collect loop).'
responses:
'200':
description: A page of documents + counts + nextCursor
content:
application/json:
schema:
type: object
properties:
total:
type: integer
description: Total documents matching the filter (not just this page).
unretrieved:
type: integer
description: How many matching documents are still uncollected.
nextCursor:
type: string
nullable: true
description: Pass back as `cursor` to fetch the next page; null when there are no more.
items:
type: array
items:
type: object
properties:
journalNo:
type: string
example: OUT-000123
direction:
type: string
enum:
- inbound
- outbound
example: outbound
type:
type: string
example: pain.001
sourceFormat:
type: string
example: pain.001.001.09
bankKey:
type: string
example: nordea-dk
summary:
type: string
example: SEPA — 1 payment
status:
type: string
example: converted
collected:
type: boolean
description: Whether this document has been collected (marked retrieved) yet.
collectedAt:
type: string
format: date-time
nullable: true
description: When it was first collected (null if not yet).
createdAt:
type: string
format: date-time
expiresAt:
type: string
format: date-time
nullable: true
required:
- total
- unretrieved
- items
example:
total: 42
unretrieved: 3
nextCursor: null
items:
-
journalNo: OUT-000123
direction: outbound
type: pain.001
sourceFormat: pain.001.001.09
bankKey: nordea-dk
summary: SEPA — 1 payment
status: converted
collected: false
collectedAt: null
createdAt: 2026-06-20T10:31:00Z
expiresAt: null
'401':
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/journal/documents/{id}':
get:
tags:
- Journal
summary: Fetch one journal document with its full (decrypted) payload
description: 'Returns the document including its canonical/normalised payload and, for seeded/generated docs, the raw `xml` for download. Tenant-scoped.'
security:
-
ApiKeyAuth: []
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Document + payload
content:
application/json:
schema:
$ref: '#/components/schemas/JournalDocument'
'401':
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Not found (or not in this tenant's scope)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/journal/documents/{id}/payments':
get:
tags:
- Journal
summary: Per-payment lines within an outbound batch (the drawer)
security:
-
ApiKeyAuth: []
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: 'Per-line payments (payee, amount, endToEndId, status)'
content:
application/json:
schema:
type: object
properties:
payments:
type: array
items:
type: object
'401':
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/journal/documents/{id}/file':
get:
tags:
- Journal
summary: Download the generated pain.001 XML for an outbound document
description: 'Returns the stored payment file as an `application/xml` attachment. 404 if the document isn''t in scope, isn''t outbound, or has no generated XML. Downloading does NOT mark the document retrieved.'
security:
-
ApiKeyAuth: []
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: The pain.001 XML (attachment)
content:
application/xml:
schema:
type: string
'404':
description: No downloadable file for this document
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/journal/payment-types:
get:
tags:
- Journal
summary: Tally of outbound payments grouped by payment type
description: 'Lightweight GROUP BY over the plaintext summary column (no payload decrypt), weighted by transaction count: a tally of your outbound payments grouped by payment type.'
security:
-
ApiKeyAuth: []
-
SessionToken: []
parameters:
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Per-type counts + total
content:
application/json:
schema:
type: object
properties:
counts:
type: object
additionalProperties:
type: integer
total:
type: integer
sampled:
type: integer
'401':
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/journal/reconciliation:
get:
tags:
- Journal
summary: 'Reconciliation view: correlate outbound payments with pain.002 + camt evidence'
description: 'For each outbound payment, returns its real-world state (cleared / rejected / pending) and the matching evidence, by correlating inbound pain.002 status reports and camt.053/054 statement entries 1:1.'
security:
-
ApiKeyAuth: []
-
SessionToken: []
parameters:
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Per-payment reconciliation + evidence
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
type: object
properties:
documentId:
type: string
journalNo:
type: string
messageId:
type: string
state:
type: string
enum:
- cleared
- rejected
- cancelled
- pending
evidence:
type: object
'401':
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/journal/events:
get:
tags:
- Journal
summary: Activity-log events (audit trail) for a company
security:
-
ApiKeyAuth: []
-
SessionToken: []
parameters:
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Events newest-first
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
type: object
'401':
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/inbound/payment-status:
post:
tags:
- Journal
summary: Push a pain.002 status report → reconcile the outbound payment
description: Accepts a raw pain.002 (or normalised status); matches it to the original outbound by MsgId/endToEndId and advances the payment status. Authenticated.
security:
-
ApiKeyAuth: []
-
SessionToken: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- companyId
- content
properties:
companyId:
type: string
description: Your company id. platformId is inferred from your API key — do not send it.
bankKey:
type: string
content:
type: string
description: Raw pain.002 XML.
responses:
'200':
description: Reconciled (status advanced)
content:
application/json:
schema:
type: object
'401':
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
description: Unparseable / unmatched status
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/inbound/statement:
post:
tags:
- Journal
summary: 'Parse a camt.053/054 / MT940 statement → normalised JSON (preview only, not stored)'
description: 'Stateless parse-only preview: the raw file body is normalised and returned but NOT journaled (use POST /journal/ingest to store). A camt.053 may carry several (one per account); ALL are returned in the `items` list envelope.'
security:
-
ApiKeyAuth: []
-
SessionToken: []
requestBody:
required: true
content:
text/plain:
schema:
type: string
description: Raw camt.053/054 XML or MT940 text.
responses:
'200':
description: Normalised statement(s)
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
type: object
'400':
description: Empty body or unrecognised format
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
description: Unparseable file
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/webhooks:
get:
tags:
- Webhooks
summary: List webhook subscriptions
security:
-
SessionToken: []
parameters:
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Subscriptions
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
type: object
'401':
description: Sign in required
post:
tags:
- Webhooks
summary: Create a webhook subscription (Admin)
description: Registers an HTTPS endpoint to receive signed event callbacks. Admin only. The subscription field is `eventTypes` (an array of event names); a typo like `events` is rejected (400) rather than silently subscribing to everything. Omit `eventTypes` entirely to receive all events.
security:
-
SessionToken: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
- url
properties:
platformId:
type: string
companyId:
type: string
url:
type: string
format: uri
eventTypes:
type: array
items:
type: string
minItems: 1
description: 'Event names to subscribe to, e.g. ["payment.sent", "payment.rejected"]. Omit to receive all events; an explicitly-empty array is rejected.'
responses:
'201':
description: Subscription created (returns the signing secret once)
content:
application/json:
schema:
type: object
'401':
description: Sign in required
'403':
description: Admin required
'/webhooks/{id}':
delete:
tags:
- Webhooks
summary: Delete a webhook subscription (Admin)
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Deleted
'401':
description: Sign in required
'403':
description: Admin required
'404':
description: Not found
'/webhooks/{id}/enable':
post:
tags:
- Webhooks
summary: Enable a disabled webhook (Admin)
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
properties:
platformId:
type: string
companyId:
type: string
responses:
'200':
description: Webhook enabled
content:
application/json:
schema:
type: object
'401':
description: Sign in required
'403':
description: Admin required
'404':
description: Not found
'/webhooks/{id}/disable':
post:
tags:
- Webhooks
summary: Disable a webhook without deleting it (Admin)
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
properties:
platformId:
type: string
companyId:
type: string
responses:
'200':
description: Webhook disabled
content:
application/json:
schema:
type: object
'401':
description: Sign in required
'403':
description: Admin required
'404':
description: Not found
'/webhooks/{id}/test':
post:
tags:
- Webhooks
summary: Fire a signed test event to the webhook URL (Admin)
description: Sends a signed ping to the endpoint and returns the delivery result.
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- platformId
- companyId
properties:
platformId:
type: string
companyId:
type: string
responses:
'200':
description: Delivery result of the test event
content:
application/json:
schema:
type: object
'401':
description: Sign in required
'403':
description: Admin required
'404':
description: Not found
'/webhooks/{id}/deliveries':
get:
tags:
- Webhooks
summary: List delivery attempts for a webhook (Admin)
description: 'Per-attempt delivery log so no event is silently lost. Newest first, keyset-paginated: pass `cursor` = the previous page''s `nextCursor` to continue. `limit` defaults to 50, max 200.'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
-
name: cursor
in: query
required: false
schema:
type: string
description: Opaque cursor from the previous page's `nextCursor`. (`after` is accepted as a deprecated alias.)
-
name: limit
in: query
required: false
schema:
type: integer
default: 50
maximum: 200
responses:
'200':
description: A page of delivery attempts
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
$ref: '#/components/schemas/WebhookDelivery'
nextCursor:
type: string
nullable: true
description: Last delivery id in this page when a full page was returned; null when no more.
'401':
description: Sign in required
'403':
description: Admin required
'404':
description: Webhook not found
'/webhooks/{id}/deliveries/{deliveryId}/redeliver':
post:
tags:
- Webhooks
summary: Replay a single delivery (Admin)
description: Re-enqueues the original event payload as a NEW delivery attempt (a new row — the original failure record is preserved). A fresh signature is computed at send time. 404 if the delivery is unknown or not in a failed/delivered state.
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
-
name: deliveryId
in: path
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Re-enqueued
content:
application/json:
schema:
type: object
properties:
redelivered:
type: boolean
deliveryId:
type: string
description: Id of the NEW delivery row.
'401':
description: Sign in required
'403':
description: Admin required
'404':
description: Delivery not found / not redeliverable
'/webhooks/{id}/redeliver-failed':
post:
tags:
- Webhooks
summary: Bulk-replay failed deliveries since a timestamp (Admin)
description: 'Re-enqueues `failed` deliveries for the webhook created at/after `since`. BOUNDED: a single call replays at most a fixed cap (500); when a full page is returned, `nextCursor` is non-null — pass it back as `cursor` to drain the rest. Idempotent: a replay id is derived from the source delivery, so a repeated/overlapping window cannot double-enqueue.'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
-
name: since
in: query
required: true
schema:
type: string
format: date-time
description: ISO timestamp — only failed deliveries created at/after this are replayed.
-
name: cursor
in: query
required: false
schema:
type: string
description: Opaque cursor from a prior page's `nextCursor` to continue draining.
responses:
'200':
description: Re-enqueued count + drain cursor
content:
application/json:
schema:
type: object
properties:
count:
type: integer
nextCursor:
type: string
nullable: true
'400':
description: Missing/invalid since
'401':
description: Sign in required
'403':
description: Admin required
'404':
description: Webhook not found
'/webhooks/{id}/re-enable':
post:
tags:
- Webhooks
summary: Re-enable an auto-disabled webhook (Admin)
description: 'Clears an auto-disabled state: resets `consecutiveFailures` to 0 and clears `disabledAt`/`disabledReason`. 400 if the webhook is not currently disabled.'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
responses:
'200':
description: Re-enabled
content:
application/json:
schema:
type: object
properties:
webhookId:
type: string
enabledAt:
type: string
format: date-time
'400':
description: Webhook is not disabled
'401':
description: Sign in required
'403':
description: Admin required
'404':
description: Webhook not found
'/webhooks/{id}/rotate-secret':
post:
tags:
- Webhooks
summary: Rotate the signing secret with an overlap window (Admin)
description: 'Generates a new signing secret and returns it ONCE. The previous secret is retained as a ''pending'' secret and BOTH signatures verify during the overlap window (`overlapHours`, default 24, max 72), so the receiver can update its key without dropping in-flight deliveries. Outbound deliveries are immediately signed with the NEW secret. After the window (housekeeping) the old secret is dropped.'
security:
-
SessionToken: []
parameters:
-
name: id
in: path
required: true
schema:
type: string
-
name: companyId
in: query
required: true
schema:
type: string
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
overlapHours:
type: integer
default: 24
minimum: 1
maximum: 72
description: How long the old secret stays valid.
responses:
'200':
description: Rotated — newSecret shown once
content:
application/json:
schema:
type: object
properties:
webhookId:
type: string
newSecret:
type: string
description: The new signing secret (whsec_…). Distribute to the receiver; not retrievable again.
overlapUntil:
type: string
format: date-time
'400':
description: Invalid body
'401':
description: Sign in required
'403':
description: Admin required
'404':
description: Webhook not found
/auth/login:
post:
tags:
- Auth
summary: 'Log in (PUBLIC): returns a session token + sets the session cookie'
description: 'PUBLIC. Exchanges subdomain + email + password for a signed session. If 2FA is enabled, returns a step-up challenge instead (complete it at POST /auth/2fa).'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- subdomain
- email
- password
properties:
subdomain:
type: string
example: demo
email:
type: string
format: email
password:
type: string
responses:
'200':
description: Logged in (token) OR a 2FA step-up challenge
content:
application/json:
schema:
type: object
properties:
token:
type: string
twoFactorRequired:
type: boolean
'401':
description: Invalid credentials
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
x-codeSamples:
-
lang: Shell
label: cURL
source: |
curl -X POST https://your-host/auth/login \
-H 'Content-Type: application/json' \
-d '{"subdomain":"demo","email":"you@acme.com","password":"…"}'
-
lang: JavaScript
label: Fetch
source: |
const res = await fetch("https://your-host/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({"subdomain":"demo","email":"you@acme.com","password":"…"}),
});
const data = await res.json();
-
lang: Python
label: Python
source: |
import requests
resp = requests.request(
"POST", "https://your-host/auth/login",
json={"subdomain":"demo","email":"you@acme.com","password":"…"},
)
resp.raise_for_status()
data = resp.json()
/2fa/setup:
post:
tags:
- Auth
summary: Begin 2FA enrollment (signed-in user) — returns a TOTP secret + otpauth URI
description: Generates a NOT-yet-active TOTP secret for the signed-in user and returns its otpauth:// URI (render as a QR). Activate it with POST /2fa/confirm. Session-only.
security:
-
SessionToken: []
responses:
'200':
description: Pending secret + otpauth URI
content:
application/json:
schema:
type: object
properties:
secret:
type: string
otpauthUri:
type: string
issuer:
type: string
'401':
description: Sign in first
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/2fa/confirm:
post:
tags:
- Auth
summary: Confirm 2FA enrollment with a TOTP code — returns one-time backup codes
description: Verifies a code against the pending secret and activates 2FA. The backup codes are returned EXACTLY ONCE — surface + warn. Session-only; throttled per user.
security:
-
SessionToken: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- code
properties:
code:
type: string
description: 6-digit TOTP code
responses:
'200':
description: Enabled + one-time backup codes
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
backupCodes:
type: array
items:
type: string
'400':
description: Setup not started
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'409':
description: Already enabled
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
description: Code didn't match
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: Too many attempts (Retry-After)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/2fa/disable:
post:
tags:
- Auth
summary: Disable 2FA (requires a fresh code; blocked when the company forces 2FA)
description: Turns off the signed-in user's 2FA after verifying a fresh TOTP or backup code; refused (403) when the company requires 2FA. Revokes the user's OTHER sessions. Session-only; throttled.
security:
-
SessionToken: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
code:
type: string
backupCode:
type: string
responses:
'200':
description: Disabled
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
'403':
description: Company requires 2FA
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'409':
description: Not enabled
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
description: A valid code is required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: Too many attempts (Retry-After)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/company/require-2fa:
post:
tags:
- Auth
summary: 'Admin: set the company-wide 2FA requirement'
description: 'Admin-only. When enabled, every user in the company must enroll in 2FA. Distinct from the per-user `enabled` state — the response field is `require2fa` (company policy).'
security:
-
SessionToken: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- enabled
properties:
enabled:
type: boolean
responses:
'200':
description: Updated company policy
content:
application/json:
schema:
type: object
properties:
require2fa:
type: boolean
'403':
description: Admin role required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/auth/me:
get:
tags:
- Auth
summary: Current principal (who am I)
security:
-
SessionToken: []
responses:
'200':
description: The signed-in user + roles + company
content:
application/json:
schema:
type: object
'401':
description: Not signed in
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/auth/logout:
post:
tags:
- Auth
summary: Log out (invalidates the session)
security:
-
SessionToken: []
responses:
'200':
description: Logged out
/auth/platform-info:
get:
tags:
- Auth
summary: Public platform branding for the login page
description: 'Returns the branding for the platform resolved from the request''s subdomain (no auth — called before login). On a known subdomain: the platform''s display name + subdomain. Otherwise the generic BankConnector branding. `logoUrl` is reserved for a later phase (null for now).'
responses:
'200':
description: Platform branding
content:
application/json:
schema:
type: object
required:
- platformId
- displayName
- subdomain
- logoUrl
properties:
platformId:
type: string
nullable: true
displayName:
type: string
subdomain:
type: string
nullable: true
logoUrl:
type: string
nullable: true
/auth/2fa:
post:
tags:
- Auth
summary: Complete a 2FA login step-up (PUBLIC)
description: PUBLIC. Submits the TOTP (or backup) code against the challenge from POST /auth/login to obtain the session.
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- challenge
- code
properties:
challenge:
type: string
description: The challenge id from /auth/login.
code:
type: string
backupCode:
type: string
responses:
'200':
description: Session granted
content:
application/json:
schema:
type: object
properties:
token:
type: string
'401':
description: Invalid code
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'/banks/{bankKey}/status':
get:
tags:
- Discovery
summary: Whether a bank is supported + its channel/payment readiness (PUBLIC)
parameters:
-
name: bankKey
in: path
required: true
schema:
$ref: '#/components/schemas/BankKey'
responses:
'200':
description: Bank support status
content:
application/json:
schema:
type: object
'404':
description: Unknown bank
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
x-sandbox: true
components:
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
description: 'Machine / ERP integration credential. Tenant scope is derived from the key: never from the request body.'
SessionToken:
type: apiKey
in: header
name: X-Session-Token
description: Signed-in human session (the web app). The browser uses an httpOnly cookie + CSRF instead.
schemas:
BankKey:
type: string
enum:
- bnp-paribas-bh
- bnp-paribas-kw
- bnp-paribas-my
- bnp-paribas-qa
- bnp-paribas-sa
- bnp-paribas-vn
- bnz-nz
- bpi-ph
- banca-intensa-rs
- cba-au
- cba-nz
- citibank-in
- citibank-my
- citibank-ph
- citibank-th
- citibank-tw
- citibank-vn
- deutsche-bank-in
- deutsche-bank-pt
- hsbc-ae
- hsbc-au
- hsbc-cn
- hsbc-eg
- hsbc-hk
- hsbc-id
- hsbc-in
- hsbc-jp
- hsbc-kr
- hsbc-mx
- hsbc-my
- hsbc-nz
- hsbc-ph
- hsbc-sg
- hsbc-tr
- hsbc-tw
- handelsbanken-cn
- handelsbanken-hk
- handelsbanken-sg
- jp-morgan-chase-au
- otp-bank-me
- seb-sg
- standard-bank-za
- standard-chartered-my
- standard-chartered-sg
- unicredit-ba
- unicredit-rs
- bnp-paribas-br
- citibank-us
- deutsche-bank-us
- hsbc-br
- hsbc-us
- handelsbanken-us
- jp-morgan-chase-ca
- scotia-ca
- tdcanadatrust-ca
- abn-amro-be
- alior-bank-pl
- bnp-paribas-bg
- bnp-paribas-cz
- bnp-paribas-es
- bnp-paribas-gb
- bnp-paribas-hu
- bank-of-america-it
- csob-cz
- ceska-sporitelna-cz
- citibank-fr
- citibank-gb
- commerzbank-cz
- commerzbank-hu
- commerzbank-sk
- credit-lyonnaise-fr
- dsk-bank-bg
- danske-bank-be
- danske-bank-ie
- deutsche-bank-be
- deutsche-bank-fr
- deutsche-bank-hu
- dnb-gb
- dnb-se
- eik-banki-fo
- eika-alliance-no
- groenbank-gl
- hsbc-be
- hsbc-es
- hsbc-fr
- hsbc-ie
- hsbc-it
- handelsbanken-ee
- handelsbanken-fi
- handelsbanken-gb
- handelsbanken-lt
- handelsbanken-lu
- handelsbanken-lv
- ing-fr
- jp-morgan-chase-be
- jp-morgan-chase-gb
- jp-morgan-chase-lu
- natwest-gb
- nordoya-spar-fo
- otp-bank-hr
- pbz-hr
- bank-pekao-pl
- raiffeisen-bank-cz
- seb-fi
- seb-gb
- seb-no
- societe-generale-lu
- sparoresund-se
- sparebank-1-alliance-no
- swedbank-fi
- swedbank-no
- unicredit-bg
- unicredit-it
- unicredit-ro
- unicredit-sk
- werhahn-bank-de
- zagrebacka-hr
- danske-dk
- deutsche-bank-de
- nordea-dk
- bbva-es
- caixabank-es
- pko-bank-polski-pl
- vub-banka-sk
- danske-fi
- danske-gb
- danske-pl
- seb-ee
- seb-lv
- seb-lt
- swedbank-ee
- swedbank-lv
- swedbank-lt
- sr-bank-no
- ing-nl
- swedbank-se
- svb-us
- seb-se
- dnb-no
- hsbc-gb
- jpmorgan-us
- nordea-se
- nordea-no
- nordea-fi
- danske-se
- danske-no
- abn-amro-nl
- nord-lb-de
- aareal-bank-de
- apobank-de
- bank-fuer-sozialwirtschaft-de
- carl-plump-de
- loebbecke-de
- bayernlb-de
- berliner-sparkasse-de
- sparkasse-bremen-de
- hsbc-de
- haspa-de
- hauck-aufhaeuser-de
- berenberg-de
- jyske-bank-de
- helaba-de
- naspa-de
- nordea-de
- olb-de
- postbank-de
- procredit-de
- saarlb-de
- santander-de
- societe-generale-de
- sparkasse-darmstadt-de
- sparkasse-fuerth-de
- sparkasse-heidelberg-de
- sparkasse-koelnbonn-de
- suedwestbank-de
- wells-fargo-us
- sparkasse-en-de
- barclays-gb
- bnp-paribas-fr
- bnp-be
- bnp-pl
- bnp-it
- bnp-lu
- berliner-volksbank-de
- landesbank-bw-de
- ing-de
- erste-group-at
- raiffeisen-bank-international-at
- bank-austria-at
- oberbank-at
- postfinance-ch
- raiffeisen-ch
- zurcher-kantonalbank-ch
- abn-amro-de
- bnp-de
- danske-de
- handelsbanken-de
- seb-de
- deutsche-at
- commerzbank-nl
- danske-nl
- deutsche-nl
- hsbc-nl
- handelsbanken-nl
- volksbank-raiffeisenbank-de
- ubs-ch
- rbc-ca
- deutsche-bank-br
- sydbank-dk
- bmo-ca
- jyske-bank-dk
- spar-nord-dk
- arbejdernes-dk
- ringkjobing-dk
- sjaelland-fyn-dk
- commerzbank-de
- credit-agricole-fr
- handelsbanken-se
- rabobank-nl
- de-volksbank-nl
- triodos-nl
- van-lanschot-nl
- nibc-nl
- bng-nl
- nwb-nl
- bunq-nl
- bank-mendes-gans-nl
- credit-mutuel-fr
- credit-suisse-ch
- cic-fr
- arkea-fr
- cibc-ca
- unicredit-de
- unicredit-cz
- societe-generale-fr
- lloyds-gb
- standard-chartered-gb
- op-financial-group-fi
- ing-be
- ing-pl
- ing-ro
- ing-hu
- ing-cz
- ing-bg
- bank-of-america-us
- us-bank-us
- truist-us
- pnc-us
- mbank-pl
- aib-ie
- eurobank-gr
- splitska-hr
- andelskassen-faelleskassen-dk
- danske-andelskassers-bank-dk
- den-jyske-sparekasse-dk
- faster-andelskasse-dk
- frorup-andelskasse-dk
- froslev-mollerup-sparekasse-dk
- fynske-bank-dk
- gronlandsbanken-gl
- hvidbjerg-bank-dk
- lollands-bank-dk
- laegernes-bank-dk
- merkur-andelskasse-dk
- mons-bank-dk
- nykredit-bank-dk
- vestjyskbank-dk
- foroya-banki-fo
- banknordik-dk
- betri-banki-fo
- borbjerg-sparekasse-dk
- dragsholm-sparekasse-dk
- sydjysk-sparekasse-dk
- klim-sparekasse-dk
- lan-og-spar-bank-dk
- middelfart-sparekasse-dk
- nordoya-sparikassi-fo
- rise-sparekasse-dk
- ronde-sparekasse-dk
- sparekassen-balling-dk
- sparekassen-bredebro-dk
- sparekassen-danmark-dk
- sparekassen-kronjylland-dk
- spks-for-nr-nebel-og-omegn-dk
- sparekassen-thy-dk
- suduroyar-sparikassi-fo
- sonderha-horsted-sparekasse-dk
- djurslands-bank-dk
- kreditbanken-dk
- nordfyns-bank-dk
- skjern-bank-dk
example: nordea-dk
description: 'A registered bank profile key, `-` (e.g. danske-dk).'
JournalPaymentRequest:
type: object
required:
- companyId
- bankKey
- payment
description: 'platformId is INFERRED from your API key — do not send it. Pass only `companyId`, `bankKey`, and the `payment` object.'
properties:
companyId:
type: string
description: The company this payment belongs to. (platformId is inferred from your API key — do not send it.)
bankKey:
$ref: '#/components/schemas/BankKey'
payment:
$ref: '#/components/schemas/PaymentInstruction'
environment:
type: string
enum:
- test
- production
description: Which connection delivers the payment. Defaults to production; pass "test" to dispatch over the bank's test channel.
validate:
type: boolean
description: Also XSD-validate the generated XML.
idempotencyKey:
type: string
description: Alternative to the Idempotency-Key header.
JournalDocument:
type: object
properties:
id:
type: string
journalNo:
type: string
example: OUT-000123
platformId:
type: string
companyId:
type: string
direction:
type: string
enum:
- inbound
- outbound
example: outbound
description: '"outbound" for a pain.001 you submit; "inbound" for a bank file you receive (camt.05x / pain.002).'
type:
type: string
example: pain.001
bankKey:
type: string
environment:
type: string
enum:
- test
- production
status:
type: string
example: converted
summary:
type: string
transactionCount:
type: integer
totals:
type: array
items:
type: object
properties:
currency:
type: string
amount:
type: string
xml:
type: string
description: The generated bank file (pain.001 XML). Returned by POST /journal/payments and single-document fetches; omitted from list summaries.
deliveryMode:
type: string
enum:
- host-to-host
- file-return
description: How an outbound payment is delivered.
createdAt:
type: string
format: date-time
BankConnection:
type: object
properties:
id:
type: string
bankKey:
$ref: '#/components/schemas/BankKey'
bankName:
type: string
example: Nordea Danmark
environment:
type: string
enum:
- test
- production
channelType:
type: string
enum:
- sftp
- webservice
- ebics
active:
type: boolean
steps:
type: object
description: Setup wizard progress flags.
properties:
introEmailSent:
type: boolean
pgpGenerated:
type: boolean
bankPublicKeyAdded:
type: boolean
serverInfoAdded:
type: boolean
sftpUserAdded:
type: boolean
description: 'true once both the SFTP username and SSH key pair have been set. These are a single combined operation via POST /connections/{id}/sftp-user.'
approversAssigned:
type: boolean
certsIssued:
type: boolean
bankCertAdded:
type: boolean
ebicsKeysGenerated:
type: boolean
ebicsBankKeysFetched:
type: boolean
wsConfigAdded:
type: boolean
sshPublicKey:
type: string
description: Public half of the SSH key (for SFTP).
pgpPublicKey:
type: string
description: Public half of our PGP key (for SFTP).
approverUserIds:
type: array
items:
type: string
createdAt:
type: string
format: date-time
ConnectionWithReadiness:
type: object
properties:
connection:
$ref: '#/components/schemas/BankConnection'
readiness:
type: object
description: 'Current go-live checklist: which prerequisites are met and which are missing.'
properties:
ready:
type: boolean
missing:
type: array
items:
type: string
score:
type: integer
minimum: 0
maximum: 100
ValidationIssue:
type: object
required:
- code
- level
- message
properties:
code:
type: string
example: BANK_BIC_REQUIRED
level:
type: string
enum:
- generic
- bank
- payment-type
example: bank
description: Which validation tier raised the issue. A `BANK_*` code is level `bank`; a `GEN_*` code is `generic`; a payment-type rule is `payment-type`.
severity:
type: string
enum:
- error
- warning
description: Defaults to "error" when absent. "warning" issues don't block the payment.
message:
type: string
example: Bank "nordea-dk" requires a BIC for the debtor.
path:
type: string
example: 'payments[0].debtor.agent.bic'
bank:
type: string
example: nordea-dk
paymentType:
type: string
example: sepa
ValidationOutcome:
type: object
required:
- valid
- issues
properties:
valid:
type: boolean
issues:
type: array
items:
$ref: '#/components/schemas/ValidationIssue'
ErrorResponse:
type: object
required:
- error
properties:
error:
type: object
required:
- code
- message
properties:
code:
type: string
description: 'Stable, snake_case, non-localised error identifier. Branch on this.'
example: validation_failed
message:
type: string
description: Human-readable text. May change; do not branch on it.
details:
type: object
description: 'Optional structured context. For validation errors, contains `issues`.'
properties:
issues:
type: array
items:
$ref: '#/components/schemas/ValidationIssue'
WebhookDelivery:
type: object
description: One webhook delivery attempt-record.
properties:
id:
type: string
example: whd_…
webhookId:
type: string
example: wh_…
platformId:
type: string
companyId:
type: string
eventType:
type: string
example: payment.sent
payload:
type: object
description: The original event envelope (type/message/at/data).
status:
type: string
enum:
- delivered
- failed
- pending
httpStatus:
type: integer
nullable: true
lastError:
type: string
nullable: true
attempts:
type: integer
deliveredAt:
type: string
format: date-time
nullable: true
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
OrganisationId:
type: object
required:
- id
properties:
id:
type: string
maxLength: 35
example: DK12345678
scheme:
type: string
maxLength: 35
example: CUST
issuer:
type: string
maxLength: 35
Account:
type: object
properties:
iban:
type: string
example: DK5000400440116243
currency:
type: string
minLength: 3
maxLength: 3
example: EUR
other:
type: object
required:
- id
- scheme
properties:
id:
type: string
maxLength: 34
example: 5050-1055
scheme:
type: string
maxLength: 35
example: BGNR
description: Exactly one of `iban` or `other` must be provided.
Agent:
type: object
properties:
bic:
type: string
example: DEUTDEFF
clearing:
type: object
required:
- system
- memberId
properties:
system:
type: string
maxLength: 35
example: GBDSC
memberId:
type: string
maxLength: 35
example: '401234'
name:
type: string
maxLength: 140
Party:
type: object
required:
- name
- account
properties:
name:
type: string
maxLength: 140
example: Acme Corp ApS
country:
type: string
minLength: 2
maxLength: 2
example: DK
account:
type: object
properties:
iban:
type: string
example: DK5000400440116243
currency:
type: string
minLength: 3
maxLength: 3
example: EUR
other:
type: object
required:
- id
- scheme
properties:
id:
type: string
maxLength: 34
example: 5050-1055
scheme:
type: string
maxLength: 35
example: BGNR
description: Exactly one of `iban` or `other` must be provided.
agent:
type: object
properties:
bic:
type: string
example: DEUTDEFF
clearing:
type: object
required:
- system
- memberId
properties:
system:
type: string
maxLength: 35
example: GBDSC
memberId:
type: string
maxLength: 35
example: '401234'
name:
type: string
maxLength: 140
organisationId:
type: object
required:
- id
properties:
id:
type: string
maxLength: 35
example: DK12345678
scheme:
type: string
maxLength: 35
example: CUST
issuer:
type: string
maxLength: 35
CreditorReference:
type: object
required:
- value
properties:
type:
type: string
enum:
- scor
- kid
- ocr
- fik
value:
type: string
maxLength: 35
example: RF18539007547034
ReferredDocument:
type: object
required:
- type
properties:
type:
type: string
enum:
- invoice
- credit-note
number:
type: string
maxLength: 35
example: INV-2026-588
date:
type: string
format: date
example: 2026-05-01
amount:
type: string
example: '1600.00'
pattern: '^\d{1,15}\.\d{2}$'
Remittance:
type: object
properties:
unstructured:
type: array
maxItems: 10
items:
type: string
maxLength: 140
description: Free-text advice lines (Ustrd). Auto-derived from documents if omitted.
documents:
type: array
items:
$ref: '#/components/schemas/ReferredDocument'
description: 'Invoices and credit notes: each emitted as a structured Strd block.'
creditorReference:
type: object
required:
- value
properties:
type:
type: string
enum:
- scor
- kid
- ocr
- fik
value:
type: string
maxLength: 35
example: RF18539007547034
additionalInfo:
type: array
items:
type: string
maxLength: 35
Transaction:
type: object
required:
- endToEndId
- amount
- currency
- creditor
properties:
endToEndId:
type: string
maxLength: 35
example: INV-2026-588
instructionId:
type: string
maxLength: 35
amount:
type: string
example: '1500.00'
pattern: '^\d{1,15}\.\d{2}$'
currency:
type: string
minLength: 3
maxLength: 3
example: EUR
creditor:
$ref: '#/components/schemas/Party'
remittance:
type: object
properties:
unstructured:
type: array
maxItems: 10
items:
type: string
maxLength: 140
description: Free-text advice lines (Ustrd). Auto-derived from documents if omitted.
documents:
type: array
items:
$ref: '#/components/schemas/ReferredDocument'
description: 'Invoices and credit notes: each emitted as a structured Strd block.'
creditorReference:
type: object
required:
- value
properties:
type:
type: string
enum:
- scor
- kid
- ocr
- fik
value:
type: string
maxLength: 35
example: RF18539007547034
additionalInfo:
type: array
items:
type: string
maxLength: 35
Payment:
type: object
required:
- paymentId
- executionDate
- debtor
- transactions
properties:
paymentId:
type: string
maxLength: 35
example: PMT-0001
paymentType:
type: string
enum:
- sepa
- sepa-instant
- domestic
- international
- split
- fik
- kid
- bankgiro
- plusgiro
- bacs
- chaps
- faster-payment
- ach
- wire
- ch-domestic
- ch-qr
- eft
- ca-wire
- br-domestic
- boleto
- pix
example: sepa
executionDate:
type: string
format: date
example: 2026-06-04
debtor:
$ref: '#/components/schemas/Party'
transactions:
type: array
minItems: 1
items:
$ref: '#/components/schemas/Transaction'
PaymentInstruction:
type: object
required:
- messageId
- initiatingParty
- payments
properties:
messageId:
type: string
maxLength: 35
example: MSG-20260601-0001
creationDateTime:
type: string
format: date-time
example: 2026-06-01T10:30:00Z
initiatingParty:
type: object
required:
- name
properties:
name:
type: string
maxLength: 140
example: Acme Corp ApS
organisationId:
type: object
required:
- id
properties:
id:
type: string
maxLength: 35
example: DK12345678
scheme:
type: string
maxLength: 35
example: CUST
issuer:
type: string
maxLength: 35
payments:
type: array
minItems: 1
items:
$ref: '#/components/schemas/Payment'
PaymentTypeEntry:
type: object
required:
- code
- label
- description
- requires
- channel
properties:
code:
type: string
example: sepa
label:
type: string
example: SEPA Credit Transfer
description:
type: string
requires:
type: array
items:
type: string
channel:
type: string
example: SEPA / TARGET2
isoEncoding:
type: object
properties:
serviceLevel:
type: string
example: SEPA
localInstrument:
type: string
example: INST
categoryPurpose:
type: string
example: TAXS
BankProfile:
type: object
required:
- key
- bankName
- countryCode
- painVersion
- paymentTypes
properties:
key:
$ref: '#/components/schemas/BankKey'
bankName:
type: string
example: Nordea Danmark
countryCode:
type: string
example: DK
painVersion:
type: string
enum:
- pain.001.001.03
- pain.001.001.09
bankBic:
type: string
example: NDEADKKK
paymentTypes:
type: array
items:
type: object
required:
- code
- label
- description
- requires
- channel
properties:
code:
type: string
example: sepa
label:
type: string
example: SEPA Credit Transfer
description:
type: string
requires:
type: array
items:
type: string
channel:
type: string
example: SEPA / TARGET2
isoEncoding:
type: object
properties:
serviceLevel:
type: string
example: SEPA
localInstrument:
type: string
example: INST
categoryPurpose:
type: string
example: TAXS
x-demo-api-key: demo_sandbox_public_key_readonly