Build / API reference
API reference
Every endpoint your API key can call: discovery, conversion, the journal, accounts and webhooks, and your organisation's connections, approvals and users, generated from the OpenAPI spec. Do your integration work against the sandbox at https://sandbox.bankconnector.com/api: it never sends to a real bank, because every sandbox connection goes to the Demo bank. Production is https://app.bankconnector.com/api (see Going live). Every path below is under that base URL, except /health and /ready, which sit at the host root. Send BankConnector-Version: 2026-10-21 on every call; a call without it runs on 2026-06-01.
Download the spec: openapi.json, openapi.yaml. It holds exactly the operations on this page.
Discovery
Explore what banks and payment types are available
/profilesReturns every registered bank, its ISO 20022 version, and the codes of the payment types available for its country. Resolve a code against GET /payment-types for the whole catalog, or GET /banks/{bankKey}/payment-types for one bank's entries with their ISO encoding.
X-API-Key header| Status | Description |
|---|---|
| 200 | List of bank profiles |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"key": "string",
"bankName": "string",
"formerNames": [
"string"
],
"acceptedDebtorBics": [
"string"
],
"countryCode": "string",
"painVersion": "string",
"evidenceTier": "live",
"unsupported": {
"reason": "string",
"contactSupport": false
},
"brand": "string",
"paymentTypes": [
"string"
]
}
]
}curl -X GET https://sandbox.bankconnector.com/api/profiles \
-H 'X-API-Key: YOUR_KEY' \
-H 'BankConnector-Version: 2026-10-21'const res = await fetch("https://sandbox.bankconnector.com/api/profiles", {
method: "GET",
headers: {
"X-API-Key": "YOUR_KEY",
"BankConnector-Version": "2026-10-21",
},
});
const data = await res.json();import requests
resp = requests.request(
"GET", "https://sandbox.bankconnector.com/api/profiles",
headers={"X-API-Key": "YOUR_KEY", "BankConnector-Version": "2026-10-21"},
)
resp.raise_for_status()
data = resp.json()/profiles/membersGermany's Sparkassen and cooperative banks (Volksbanken, Raiffeisenbanken) are served by two group profiles, sparkasse-de and volksbank-raiffeisenbank-de, except the members with a profile of their own. Each item is one institution with its name, seat, BIC and bank codes, and bankKey, the profile that serves it. A Sparkasse also carries ebics, its EBICS server and, where known, its Host ID. Built from the Bundesbank bank-code file, the DSGV list of Sparkassen and BaFin's register of credit institutions; an institution that is not listed is not served by a group profile.
X-API-Key header| Status | Description |
|---|---|
| 200 | Every member institution |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"name": "string",
"town": "string",
"bic": "string",
"bankCode": "string",
"otherBankCodes": [
"string"
],
"formerBankCodes": [
"string"
],
"countryCode": "string",
"bankKey": "string",
"ebics": {
"url": "string",
"hostId": "string"
}
}
]
}/payment-typesAll payment types in the shared catalog with ISO 20022 encoding, descriptions, and requirements.
X-API-Key header| Status | Description |
|---|---|
| 200 | Full catalog |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"code": "string",
"label": "string",
"description": "string",
"requires": [
"string"
],
"channel": "string",
"isoEncoding": {
"serviceLevel": "string",
"localInstrument": "string",
"categoryPurpose": "string"
}
}
]
}/banks/{bankKey}/payment-typesThe payment types the bank offers, each with its full metadata. On the sandbox the answer is 403 until the organisation has a connection to that bank.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
bankKey BankKey | path | yes |
| Status | Description |
|---|---|
| 200 | Bank key/name + offered payment types with full metadata |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | On the sandbox: the organisation has no connection to this bank yet, so its payment types are not listed |
| 404 | Unknown bank |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"code": "string",
"label": "string",
"description": "string",
"requires": [
"string"
],
"channel": "string",
"isoEncoding": {
"serviceLevel": "string",
"serviceLevelProprietary": "string",
"localInstrument": "string",
"localInstrumentProprietary": "string",
"categoryPurpose": "string"
}
}
],
"bankKey": "string",
"bankName": "string",
"countryCode": "string",
"regulatoryReporting": {
"country": "string",
"authority": {
"name": "string",
"country": "string"
},
"register": "string",
"threshold": {
"amount": 0,
"currency": "string"
},
"scope": "string",
"codeDigits": 0,
"codeList": {
"publication": "string",
"url": "string",
"codes": [
{
"code": "string",
"label": "string"
}
]
},
"infoText": "expected",
"summary": "string",
"sources": [
"string"
]
}
}/banking-days/adjustReturns date unchanged with moved: false when it is a banking day for this bank and currency, and otherwise the nearest banking day in direction, with why the requested date is closed. A banking day is one the payment preview raises no calendar warning for: not a weekend, not a bank holiday in the bank's country, and open in the currency's settlement calendar (TARGET2 for EUR) and, when creditorCountry is given, in that country's. next is the first later date that clears all of them, the date the preview's own GENERIC_EXECUTION_DATE_NON_BANKING warning suggests for the same bank, currency and creditor; previous is the banking day before, for a date that must not be overshot, such as the last day of a cash discount. The date is never clamped to today.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
bankKey string | query | yes | The debtor's bank, as `GET /profiles` lists it (e.g. `danske-dk`). Its country's bank holidays close the day. |
currency string | query | yes | ISO 4217 code of the payment currency, upper case. EUR adds the TARGET2 calendar; DKK, SEK, NOK, GBP and CHF add their own country's. |
date string | query | yes | The payment date to check, `YYYY-MM-DD`, between 2000-01-01 and 2099-12-31. |
direction enum | query | yes | Which way to move a date that is not a banking day: `next` for the first banking day after it, `previous` for the last one before it (a cash-discount last day). A banking day is returned unchanged either way. |
creditorCountry string | query | no | ISO 3166-1 alpha-2 country of the creditor's account (the IBAN's first two letters), upper case. Optional: when given, that country's bank holidays close the day too, as they do at preview. |
| Status | Description |
|---|---|
| 200 | The banking day |
| 400 | A parameter is missing or invalid (`invalid_parameter`): an unknown `bankKey`, a currency or country that is not an upper-case ISO code, a date that is not a real `YYYY-MM-DD` day between 2000 and 2099, or a `direction` other than `next`/`previous`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"date": "string",
"moved": false,
"reason": "weekend",
"holidayName": "string"
}/banks/liveOne item per bank and environment where the organisation has at least one active bank connection. A connection that is deactivated or deleted drops its bank from the list on the next read, and a connection activated today is listed before any statement has arrived. A bank live in both test and production is listed twice. The items carry the bank key, its name and the environment only, never the connection itself.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | The live banks, ordered by bank key and then environment |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"bankKey": "string",
"bankName": "string",
"environment": "test",
"live": false
}
]
}/banks/{bankKey}/statusFor a supported bank, its bankKey, the deliveryMode this organisation's connection gives it (host-to-host when a live channel delivers payments) and hostToHost, whether that is so. Needs a credential and the organisation's companyId.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
bankKey BankKey | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Bank support status |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | Unknown bank |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"bankKey": "string",
"deliveryMode": "converted-only",
"hostToHost": false
}Conversion
Validate and convert canonical payment JSON
/validate/{bankKey}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.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
bankKey BankKey | path | yes | |
companyId string | query | no | The company whose timezone the execution-date rules judge in. Optional: without it, and without a selected company, those rules use UTC. |
| Property | Type | Required | Description |
|---|---|---|---|
schemaVersion | enum | no | Canonical contract version; defaults to the current version when omitted. |
messageId | string | yes | |
creationDateTime | string | no | |
initiatingParty | object | yes | |
payments | Payment[] | yes |
{
"schemaVersion": "1.0",
"messageId": "MSG-20260601-0001",
"creationDateTime": "2026-06-01T10:30:00Z",
"initiatingParty": {
"name": "Acme Corp ApS",
"organisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
},
"secondOrganisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
}
},
"payments": [
{
"paymentId": "PMT-0001",
"paymentType": "sepa",
"priority": "normal",
"chargeBearer": "DEBT",
"localInstrument": "string",
"localInstrumentProprietary": "string",
"categoryPurpose": "string",
"executionDate": "2026-06-04",
"debtor": {
"name": "Acme Corp ApS",
"country": "DK",
"postalAddress": {
"streetName": "string",
"buildingNumber": "string",
"postCode": "string",
"townName": "string",
"countrySubDivision": "string",
"addressLines": []
},
"account": {
"iban": "DK5000400440116243",
"currency": "EUR",
"other": {},
"rollNumber": "ROLL-1234567"
},
"agent": {
"bic": "DEUTDEFF",
"clearing": {},
"name": "string",
"country": "DE"
},
"organisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
}
},
"serviceUserNumber": "123456",
"ultimateDebtor": {
"name": "Acme Subsidiary GmbH",
"country": "DE",
"postalAddress": {
"streetName": "string",
"buildingNumber": "string",
"postCode": "string",
"townName": "string",
"countrySubDivision": "string",
"addressLines": []
},
"organisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
}
},
"transactions": [
{
"endToEndId": "INV-2026-588",
"instructionId": "string",
"amount": "1500.00",
"currency": "EUR",
"creditor": {},
"ultimateDebtor": {},
"ultimateCreditor": {},
"instructionForDebtorAgent": "string",
"instructionForCreditorAgent": "string",
"remittance": {},
"purposeCode": "GDDS",
"regulatoryReporting": {},
"splitPayment": {},
"hmrcRtiReference": "AB1",
"resendOf": {}
}
]
}
]
}| Status | Description |
|---|---|
| 200 | Validation outcome (valid or not: check `valid`) |
| 400 | Malformed JSON body No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | Unknown bank key |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"valid": false,
"issues": [
{
"code": "string",
"level": "generic",
"severity": "error",
"message": "string",
"path": "string",
"bank": "string"
}
]
}curl -X POST https://sandbox.bankconnector.com/api/validate/nordea-dk \
-H 'X-API-Key: YOUR_KEY' \
-H 'BankConnector-Version: 2026-10-21' \
-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"}}}]}]}'const res = await fetch("https://sandbox.bankconnector.com/api/validate/nordea-dk", {
method: "POST",
headers: {
"X-API-Key": "YOUR_KEY",
"BankConnector-Version": "2026-10-21",
"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();import requests
resp = requests.request(
"POST", "https://sandbox.bankconnector.com/api/validate/nordea-dk",
headers={"X-API-Key": "YOUR_KEY", "BankConnector-Version": "2026-10-21"},
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}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. To see a payment exactly as it will be submitted without sending it, use POST /journal/payments/preview.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
bankKey BankKey | path | yes | |
validate boolean | query | no | If true, XSD-validate the generated XML before returning it. |
painVersion string | query | no | The pain.001 version to produce, where the bank supports more than one. A version the bank does not support is a 400 `unsupported_pain_version` listing the supported ones in `details.supported`. The sandbox ignores it: every file it builds is pain.001.001.09, whatever the bank. |
companyId string | query | no | The company whose timezone the execution-date rules judge in. Optional: without it, and without a selected company, those rules use UTC. |
| Property | Type | Required | Description |
|---|---|---|---|
schemaVersion | enum | no | Canonical contract version; defaults to the current version when omitted. |
messageId | string | yes | |
creationDateTime | string | no | |
initiatingParty | object | yes | |
payments | Payment[] | yes |
{
"schemaVersion": "1.0",
"messageId": "MSG-20260601-0001",
"creationDateTime": "2026-06-01T10:30:00Z",
"initiatingParty": {
"name": "Acme Corp ApS",
"organisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
},
"secondOrganisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
}
},
"payments": [
{
"paymentId": "PMT-0001",
"paymentType": "sepa",
"priority": "normal",
"chargeBearer": "DEBT",
"localInstrument": "string",
"localInstrumentProprietary": "string",
"categoryPurpose": "string",
"executionDate": "2026-06-04",
"debtor": {
"name": "Acme Corp ApS",
"country": "DK",
"postalAddress": {
"streetName": "string",
"buildingNumber": "string",
"postCode": "string",
"townName": "string",
"countrySubDivision": "string",
"addressLines": []
},
"account": {
"iban": "DK5000400440116243",
"currency": "EUR",
"other": {},
"rollNumber": "ROLL-1234567"
},
"agent": {
"bic": "DEUTDEFF",
"clearing": {},
"name": "string",
"country": "DE"
},
"organisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
}
},
"serviceUserNumber": "123456",
"ultimateDebtor": {
"name": "Acme Subsidiary GmbH",
"country": "DE",
"postalAddress": {
"streetName": "string",
"buildingNumber": "string",
"postCode": "string",
"townName": "string",
"countrySubDivision": "string",
"addressLines": []
},
"organisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
}
},
"transactions": [
{
"endToEndId": "INV-2026-588",
"instructionId": "string",
"amount": "1500.00",
"currency": "EUR",
"creditor": {},
"ultimateDebtor": {},
"ultimateCreditor": {},
"instructionForDebtorAgent": "string",
"instructionForCreditorAgent": "string",
"remittance": {},
"purposeCode": "GDDS",
"regulatoryReporting": {},
"splitPayment": {},
"hmrcRtiReference": "AB1",
"resendOf": {}
}
]
}
]
}| Status | Description |
|---|---|
| 200 | Bank-specific pain.001 XML. A payment that is VALID but raised non-blocking warnings still converts, warnings never block, and its response carries the `X-Validation-Warnings` header. |
| 400 | Malformed JSON body No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | Unknown bank key |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | Validation failed: returns all issues at once |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
| 503 | `overloaded`: with `?validate=true`, the XSD validators are busy; wait `Retry-After` seconds and send it again |
curl -X POST https://sandbox.bankconnector.com/api/convert/nordea-dk \
-H 'X-API-Key: YOUR_KEY' \
-H 'BankConnector-Version: 2026-10-21' \
-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"}}}]}]}'const res = await fetch("https://sandbox.bankconnector.com/api/convert/nordea-dk", {
method: "POST",
headers: {
"X-API-Key": "YOUR_KEY",
"BankConnector-Version": "2026-10-21",
"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 XMLimport requests
resp = requests.request(
"POST", "https://sandbox.bankconnector.com/api/convert/nordea-dk",
headers={"X-API-Key": "YOUR_KEY", "BankConnector-Version": "2026-10-21"},
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}Non-blocking version 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 background worker cannot run the XSD). Use the synchronous /convert/{bankKey}?validate=true instead.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
bankKey BankKey | path | yes | |
validate boolean | query | no | Not available on the async path: `true` is a 400 `validate_unsupported_on_async`. Use `POST /convert/{bankKey}?validate=true` for an XSD-validated conversion. |
painVersion string | query | no | The pain.001 version to produce, where the bank supports more than one. A version the bank does not support is a 400 `unsupported_pain_version` listing the supported ones in `details.supported`. The sandbox ignores it: every file it builds is pain.001.001.09, whatever the bank. |
companyId string | query | no | The company whose timezone the execution-date rules judge in. Optional: without it, and without a selected company, those rules use UTC. |
| Property | Type | Required | Description |
|---|---|---|---|
schemaVersion | enum | no | Canonical contract version; defaults to the current version when omitted. |
messageId | string | yes | |
creationDateTime | string | no | |
initiatingParty | object | yes | |
payments | Payment[] | yes |
{
"schemaVersion": "1.0",
"messageId": "MSG-20260601-0001",
"creationDateTime": "2026-06-01T10:30:00Z",
"initiatingParty": {
"name": "Acme Corp ApS",
"organisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
},
"secondOrganisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
}
},
"payments": [
{
"paymentId": "PMT-0001",
"paymentType": "sepa",
"priority": "normal",
"chargeBearer": "DEBT",
"localInstrument": "string",
"localInstrumentProprietary": "string",
"categoryPurpose": "string",
"executionDate": "2026-06-04",
"debtor": {
"name": "Acme Corp ApS",
"country": "DK",
"postalAddress": {
"streetName": "string",
"buildingNumber": "string",
"postCode": "string",
"townName": "string",
"countrySubDivision": "string",
"addressLines": []
},
"account": {
"iban": "DK5000400440116243",
"currency": "EUR",
"other": {},
"rollNumber": "ROLL-1234567"
},
"agent": {
"bic": "DEUTDEFF",
"clearing": {},
"name": "string",
"country": "DE"
},
"organisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
}
},
"serviceUserNumber": "123456",
"ultimateDebtor": {
"name": "Acme Subsidiary GmbH",
"country": "DE",
"postalAddress": {
"streetName": "string",
"buildingNumber": "string",
"postCode": "string",
"townName": "string",
"countrySubDivision": "string",
"addressLines": []
},
"organisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
}
},
"transactions": [
{
"endToEndId": "INV-2026-588",
"instructionId": "string",
"amount": "1500.00",
"currency": "EUR",
"creditor": {},
"ultimateDebtor": {},
"ultimateCreditor": {},
"instructionForDebtorAgent": "string",
"instructionForCreditorAgent": "string",
"remittance": {},
"purposeCode": "GDDS",
"regulatoryReporting": {},
"splitPayment": {},
"hmrcRtiReference": "AB1",
"resendOf": {}
}
]
}
]
}| Status | Description |
|---|---|
| 202 | Conversion enqueued. `warnings` is present only when the (valid) payment raised non-blocking warnings: they are returned here, on the enqueue, rather than on the job poll, whose success body is the XML file. |
| 400 | Malformed JSON body, ?validate=true (unsupported on the async path), or unsupported painVersion No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | The credential carries no organisation scope, so the job could not be fenced to an owner and the conversion is refused. An API key bound to an organisation is the async path's credential; the synchronous POST /convert/{bankKey} returns the XML directly and stores nothing. |
| 404 | Unknown bank key |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | Validation failed: returns all issues at once |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"jobId": "string",
"status": "pending",
"bankKey": "string",
"autoPaymentType": "string",
"unknownFieldsDropped": [
"string"
],
"warnings": [
{
"code": "string",
"level": "generic",
"severity": "error",
"message": "string",
"path": "string",
"bank": "string"
}
]
}/convert-jobs/{id}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).
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes |
| Status | Description |
|---|---|
| 200 | Terminal state: the converted XML once the job has succeeded. A permanently-failed (dead) job returns 200 with a `failure` object. Not the reserved `error` envelope key, which always means the REQUEST itself failed. |
| 202 | Job still pending or running, poll again. Same job-status body as the 200. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | Conversion job not found (or not owned by the caller) |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"jobId": "string",
"status": "pending",
"createdAt": "string",
"updatedAt": "string",
"failure": {
"code": "string",
"message": "string",
"details": {
"lastError": "string"
}
}
}/payment-types/suggestInfers which of the bank's payment types fits one canonical payment, from its currency, amount, creditor country and urgency. Returns the suggested code (selected, null when nothing fits), a confidence of high, medium or low, a plain-language reason and the other viable types in alternatives. Nothing is stored and no payment is created. A body that is not JSON answers 400; a payment with the wrong structure answers 422 on the current API version and 400 on older ones.
X-API-Key header| Property | Type | Required | Description |
|---|---|---|---|
bankKey | string | yes | |
payment | object | yes |
{
"bankKey": "string",
"payment": {
"debtor": {
"country": "string",
"account": {
"iban": "string",
"other": {
"id": "string",
"scheme": "string"
}
},
"agent": {
"clearing": {
"system": "string"
}
}
},
"transactions": [
{
"amount": "string",
"currency": "string",
"creditor": {
"country": "string",
"account": {},
"agent": {}
},
"remittance": {
"creditorReference": {}
}
}
]
}
}| Status | Description |
|---|---|
| 200 | The suggestion |
| 400 | `validation_failed`: the body is not JSON, or `bankKey` or `payment` is missing |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | `bank_not_found`: no bank with that key |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | `validation_failed`: `payment` is structurally wrong |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"bankKey": "string",
"selected": "string",
"confidence": "high",
"reason": "string",
"alternatives": [
{
"code": "string",
"reason": "string"
}
]
}Connections
Bank connections: reading them, their health and their files (API key or session); setting one up, channel activation and EBICS onboarding (signed-in Admin)
/connectivity/profilesRead-only metadata: which channel each bank uses (sftp / danske-ws / nordea-ca / bankconnect / ebics), its known server and endpoint details, and SFTP wizard config. No auth required. Pass view=badge for the catalogue projection (bankKey, bankName, channelType and webservice.wsVariant only), which is an order of magnitude smaller. A deployed sandbox always answers the badge projection, whether or not view was passed.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
view enum | query | no | `badge` projects each row down to identity + channel badge. Omit it for the full connection declaration. Any other value is a 400 `invalid_parameter`. |
| Status | Description |
|---|---|
| 200 | Connectivity profiles |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"bankKey": "string",
"bankName": "string",
"channelType": "sftp",
"stepper": "sftp",
"sftp": {
"gateway": "tietoevry",
"bankId": "string",
"host": "string",
"port": 0,
"uploadPath": "string",
"downloadPath": "string",
"testHost": "string",
"testPort": 0,
"testLoginDiffers": false,
"productionFirst": false,
"downloadHost": "string",
"usernameHint": "string",
"uploadTemplate": "string",
"downloadPattern": "string",
"additionalDownloadPaths": [
"string"
],
"deletesOnDownload": false,
"minPollIntervalSeconds": 0,
"instantUploadPrefix": "string",
"uploadExtension": "string",
"inputs": [
{}
],
"sshKeyBits": 0,
"pgpKeyBits": 0,
"dualAuth": false
},
"webservice": {
"wsVariant": "danske",
"endpoint": "string",
"pkiEndpoint": "string",
"receiverId": "string",
"environment": "TEST",
"environmentEvidence": {
"basis": "string",
"source": "string",
"checkedOn": "string",
"detail": "string"
},
"registrationNumber": "string",
"setupFields": {
"add": [],
"remove": [],
"copy": {}
},
"minPollIntervalSeconds": 0
},
"ebics": {
"gateway": "multivia",
"url": "string",
"hostId": "string",
"bic": "string",
"signatureVersion": "A005",
"supportedVersions": [
"2.1"
],
"keyLengthBits": 0,
"sepa": {
"sct": false,
"cor": false,
"b2b": false,
"scc": false
},
"bankKeyHashes": {
"authentication": "string",
"encryption": "string"
},
"certHashes": {
"authentication": "string",
"encryption": "string"
},
"bankKeyPinCheck": {
"checkedOn": "string",
"source": "string"
},
"bankKeyRotation": {
"validFrom": "string",
"bankKeyHashes": {},
"certHashes": {},
"check": {}
},
"keyManagementByVersion": {},
"h005": {
"upload": {},
"uploadByKind": {},
"uploadOptInByKind": {},
"download": {}
},
"h005OnFile": false,
"veuOnly": false,
"h3k": false,
"edsSupported": false,
"ebicsNotes": [
"string"
],
"noPositiveStatusReport": {
"basis": "string",
"source": "string",
"checkedOn": "string",
"detail": "string"
},
"paymentValidationPortal": {
"name": "string",
"evidence": {}
},
"statementsOnly": {
"basis": "string",
"source": "string",
"checkedOn": "string",
"detail": "string"
},
"evidence": {
"supportedVersions": {},
"signatureVersion": {},
"keyLengthBits": {},
"sepa": {},
"h005": {}
}
},
"bankPgpPublicKey": "string",
"bankPgpKeySlug": "string",
"sshKeyType": "ed25519",
"introEmail": {
"to": "string",
"subject": "string",
"body": "string"
},
"sftpSetup": {
"requirementsText": "string",
"add": [
{}
],
"remove": [
"countries"
],
"copy": {},
"counterpart": "string",
"stepCopy": {},
"screens": [
{}
]
},
"ebicsSetup": {
"add": [
{}
],
"remove": [
"request-email"
],
"copy": {},
"groups": [
{}
]
},
"notes": "string"
}
]
}/connectionsThe company's bank channels: for each, the bank and the banks it covers, the channel type (sftp, webservice, ebics), test or production, its lifecycle status, the public halves of its keys and certificate expiry dates, polling state, whether sends are paused, and inboundHealth. Private keys and passwords are never returned.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Connection list |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"id": "string",
"companyId": "string",
"bankKey": "string",
"bankName": "string",
"coveredBankKeys": [
"string"
],
"status": "setup",
"deliveryMode": "converted-only",
"environment": "test",
"channelType": "sftp",
"sftp": {
"host": "string",
"port": 0,
"username": "string",
"uploadPath": "string",
"downloadPath": "string",
"downloadHost": "string",
"answers": {},
"filename": "string",
"hostFingerprint": "string",
"hostFingerprintToCheck": "string",
"downloadHostFingerprint": "string",
"allowPlaintext": false,
"expectedInboundEncryption": "pgp",
"nextKeys": {
"pgp": {},
"ssh": {}
},
"tietoevry": {
"code": "string",
"orgNo": "string",
"custId": "string",
"bankId": "string",
"env": "test"
},
"demo": {
"outcome": "accepted",
"delaySeconds": 0,
"rejectReason": "string",
"auto": false
}
},
"webservice": {
"wsVariant": "danske",
"endpoint": "string",
"pkiEndpoint": "string",
"customerId": "string",
"senderId": "string",
"functionId": "string",
"targetId": "string",
"receiverId": "string",
"environment": "TEST",
"signingCertFingerprint": "string",
"signingCertSerial": "string",
"encryptionCertSerial": "string",
"bankCertAdded": false,
"bankConnectCamt052": false,
"answers": {}
},
"ebics": {
"url": "string",
"hostId": "string",
"partnerId": "string",
"userId": "string",
"protocolVersion": "string",
"subscriberMode": "technical",
"signatureVersion": "A005",
"state": "created",
"lettersPostedAt": "string",
"fingerprintConfirmation": {
"confirmedAt": "string",
"confirmedBy": "string",
"confirmedByName": "string",
"keyManagement": "key",
"fingerprints": {}
},
"modulusLength": 0,
"subscriberId": "string",
"subscriberSignatureCertPem": "string",
"subscriberAuthCertPem": "string",
"subscriberEncryptionCertPem": "string",
"bankAuthPublicKeyPem": "string",
"bankEncryptionPublicKeyPem": "string",
"bankKeyHashesPinned": {
"authentication": "string",
"encryption": "string"
},
"bankCertHashesPinned": {
"authentication": "string",
"encryption": "string"
},
"perUserActivation": {
"required": 0,
"active": 0
},
"signatureProfile": "T",
"keyManagement": "key",
"authorisationMode": "outside-ebics",
"authorisationConfirmation": {
"mode": "outside-ebics",
"bankMode": "outside-ebics",
"confirmedAt": "string"
},
"veuOnly": false,
"uploaderUserId": "string",
"bankParams": {
"bankName": "string",
"url": "string",
"hostId": "string",
"protocolVersions": [],
"authenticationVersions": [],
"encryptionVersions": [],
"signatureVersions": [],
"recoverySupported": false,
"preValidationSupported": false,
"clientDataDownloadSupported": false,
"downloadableOrderDataSupported": false,
"fetchedAt": "string"
},
"hev": {
"checkedAt": "string",
"versions": [],
"h005Supported": false,
"profileMismatch": {},
"ok": false,
"message": "string",
"source": "connection",
"url": "string",
"hostId": "string"
},
"discovery": {
"via": "HTD",
"fallbackFrom": {},
"customerName": "string",
"hostId": "string",
"accounts": [],
"orderInfo": [],
"users": [],
"fetchedAt": "string"
},
"agreementMismatches": [
{}
],
"agreementRefresh": {
"attemptedAt": "string",
"ok": false,
"message": "string"
},
"reportCoverage": {
"source": "contract",
"fallback": "no-agreement-record",
"reports": [],
"noStatusReportFor": []
},
"stagedRenewal": {
"subscriberMode": "technical",
"userId": "string",
"ebicsUserId": "string",
"startedAt": "string",
"ambiguous": false
},
"deactivatedAt": "string",
"sprSuspension": {
"at": "string",
"mode": "technical",
"userIds": [],
"unconfirmedUserIds": []
},
"technicalRekey": {
"stagedAt": "string",
"sentAt": "string"
}
},
"pgpFingerprint": "string",
"pgpPublicKey": "string",
"pgpExpiresAt": "string",
"bankPgpPublicKey": "string",
"useRegistryBankKey": false,
"bankKeyFingerprintPin": "string",
"bankPgpKeyInfo": {
"keyId": "string",
"fingerprint": "string",
"userId": "string",
"algorithm": "string",
"createdAt": "string",
"expiresAt": "string"
},
"sshPublicKey": "string",
"sshPublicKeyArmored": "string",
"sshFingerprint": "string",
"sshKeyType": "ed25519",
"abnXmlSigningPublicKey": "string",
"abnXmlSigningFingerprint": "string",
"steps": {
"introEmailSent": false,
"pgpGenerated": false,
"bankPublicKeyAdded": false,
"serverInfoAdded": false,
"sftpUserAdded": false,
"sftpTestingSkipped": false,
"wsConfigAdded": false,
"bankCertAdded": false,
"certsIssued": false,
"hevChecked": false,
"ebicsKeysGenerated": false,
"ebicsIniHiaSent": false,
"ebicsLetterSent": false,
"ebicsBankKeysFetched": false,
"abnXmlSigningKeyGenerated": false,
"sftpPasswordSet": false,
"approversAssigned": false
},
"ingestedFiles": [
"string"
],
"productionVerifiedAt": "string",
"connectedAt": "string",
"testChannelSupported": false,
"signingCertExpiresAt": "string",
"tlsPins": [
"string"
],
"wsCertExpiresAt": "string",
"wsBankCertExpiresAt": "string",
"wsBankSigningCertExpiresAt": "string",
"asicCertExpiresAt": "string",
"certAlertSent": {},
"pollIntervalSeconds": 0,
"lastPolledAt": "string",
"consecutivePollFailures": 0,
"pollFailureAlertSentAt": "string",
"sendsPaused": false,
"sendsPausedAt": "string",
"sendsPausedReason": "string",
"createdAt": "string",
"updatedAt": "string",
"signingCertSubject": "string",
"signingCertExpiry": "string",
"inboundHealth": {
"status": "live",
"consecutiveFailures": 0,
"threshold": 0,
"reasons": [
"string"
]
}
}
]
}/connections/{id}One connection (the same view GET /connections lists) with what setup still needs: readiness (ready, and the steps still missing), the bank group it belongs to, its production state, the SFTP setup screens where the channel is SFTP, and the registry bank key. Any company member or an API key.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Connection + readiness |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | Not found |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"connection": {
"id": "string",
"companyId": "string",
"bankKey": "string",
"bankName": "string",
"coveredBankKeys": [
"string"
],
"status": "setup",
"deliveryMode": "converted-only",
"environment": "test",
"channelType": "sftp",
"sftp": {
"host": "string",
"port": 0,
"username": "string",
"uploadPath": "string",
"downloadPath": "string",
"downloadHost": "string",
"answers": {},
"filename": "string",
"hostFingerprint": "string",
"hostFingerprintToCheck": "string",
"downloadHostFingerprint": "string",
"allowPlaintext": false,
"expectedInboundEncryption": "pgp",
"nextKeys": {
"pgp": {
"publicKey": "string",
"fingerprint": "string",
"expiresAt": "string",
"preparedAt": "string"
},
"ssh": {
"publicKey": "string",
"publicKeyArmored": "string",
"fingerprint": "string",
"type": "ed25519",
"preparedAt": "string"
}
},
"tietoevry": {
"code": "string",
"orgNo": "string",
"custId": "string",
"bankId": "string",
"env": "test"
},
"demo": {
"outcome": "accepted",
"delaySeconds": 0,
"rejectReason": "string",
"auto": false
}
},
"webservice": {
"wsVariant": "danske",
"endpoint": "string",
"pkiEndpoint": "string",
"customerId": "string",
"senderId": "string",
"functionId": "string",
"targetId": "string",
"receiverId": "string",
"environment": "TEST",
"signingCertFingerprint": "string",
"signingCertSerial": "string",
"encryptionCertSerial": "string",
"bankCertAdded": false,
"bankConnectCamt052": false,
"answers": {}
},
"ebics": {
"url": "string",
"hostId": "string",
"partnerId": "string",
"userId": "string",
"protocolVersion": "string",
"subscriberMode": "technical",
"signatureVersion": "A005",
"state": "created",
"lettersPostedAt": "string",
"fingerprintConfirmation": {
"confirmedAt": "string",
"confirmedBy": "string",
"confirmedByName": "string",
"keyManagement": "key",
"fingerprints": {
"authentication": "string",
"encryption": "string"
}
},
"modulusLength": 0,
"subscriberId": "string",
"subscriberSignatureCertPem": "string",
"subscriberAuthCertPem": "string",
"subscriberEncryptionCertPem": "string",
"bankAuthPublicKeyPem": "string",
"bankEncryptionPublicKeyPem": "string",
"bankKeyHashesPinned": {
"authentication": "string",
"encryption": "string"
},
"bankCertHashesPinned": {
"authentication": "string",
"encryption": "string"
},
"perUserActivation": {
"required": 0,
"active": 0
},
"signatureProfile": "T",
"keyManagement": "key",
"authorisationMode": "outside-ebics",
"authorisationConfirmation": {
"mode": "outside-ebics",
"bankMode": "outside-ebics",
"confirmedAt": "string"
},
"veuOnly": false,
"uploaderUserId": "string",
"bankParams": {
"bankName": "string",
"url": "string",
"hostId": "string",
"protocolVersions": [
"string"
],
"authenticationVersions": [
"string"
],
"encryptionVersions": [
"string"
],
"signatureVersions": [
"string"
],
"recoverySupported": false,
"preValidationSupported": false,
"clientDataDownloadSupported": false,
"downloadableOrderDataSupported": false,
"fetchedAt": "string"
},
"hev": {
"checkedAt": "string",
"versions": [
{}
],
"h005Supported": false,
"profileMismatch": {
"profile": [],
"host": []
},
"ok": false,
"message": "string",
"source": "connection",
"url": "string",
"hostId": "string"
},
"discovery": {
"via": "HTD",
"fallbackFrom": {
"via": "HTD",
"code": "string"
},
"customerName": "string",
"hostId": "string",
"accounts": [
{}
],
"orderInfo": [
{}
],
"users": [
{}
],
"fetchedAt": "string"
},
"agreementMismatches": [
{
"kind": "signature-class",
"configured": "string",
"bank": "string",
"consequence": "string",
"blocking": false
}
],
"agreementRefresh": {
"attemptedAt": "string",
"ok": false,
"message": "string"
},
"reportCoverage": {
"source": "contract",
"fallback": "no-agreement-record",
"reports": [
"string"
],
"noStatusReportFor": [
"string"
]
},
"stagedRenewal": {
"subscriberMode": "technical",
"userId": "string",
"ebicsUserId": "string",
"startedAt": "string",
"ambiguous": false
},
"deactivatedAt": "string",
"sprSuspension": {
"at": "string",
"mode": "technical",
"userIds": [
"string"
],
"unconfirmedUserIds": [
"string"
]
},
"technicalRekey": {
"stagedAt": "string",
"sentAt": "string"
}
},
"pgpFingerprint": "string",
"pgpPublicKey": "string",
"pgpExpiresAt": "string",
"bankPgpPublicKey": "string",
"useRegistryBankKey": false,
"bankKeyFingerprintPin": "string",
"bankPgpKeyInfo": {
"keyId": "string",
"fingerprint": "string",
"userId": "string",
"algorithm": "string",
"createdAt": "string",
"expiresAt": "string"
},
"sshPublicKey": "string",
"sshPublicKeyArmored": "string",
"sshFingerprint": "string",
"sshKeyType": "ed25519",
"abnXmlSigningPublicKey": "string",
"abnXmlSigningFingerprint": "string",
"steps": {
"introEmailSent": false,
"pgpGenerated": false,
"bankPublicKeyAdded": false,
"serverInfoAdded": false,
"sftpUserAdded": false,
"sftpTestingSkipped": false,
"wsConfigAdded": false,
"bankCertAdded": false,
"certsIssued": false,
"hevChecked": false,
"ebicsKeysGenerated": false,
"ebicsIniHiaSent": false,
"ebicsLetterSent": false,
"ebicsBankKeysFetched": false,
"abnXmlSigningKeyGenerated": false,
"sftpPasswordSet": false,
"approversAssigned": false
},
"ingestedFiles": [
"string"
],
"productionVerifiedAt": "string",
"connectedAt": "string",
"testChannelSupported": false,
"signingCertExpiresAt": "string",
"tlsPins": [
"string"
],
"wsCertExpiresAt": "string",
"wsBankCertExpiresAt": "string",
"wsBankSigningCertExpiresAt": "string",
"asicCertExpiresAt": "string",
"certAlertSent": {},
"pollIntervalSeconds": 0,
"lastPolledAt": "string",
"consecutivePollFailures": 0,
"pollFailureAlertSentAt": "string",
"sendsPaused": false,
"sendsPausedAt": "string",
"sendsPausedReason": "string",
"createdAt": "string",
"updatedAt": "string",
"signingCertSubject": "string",
"signingCertExpiry": "string",
"inboundHealth": {
"status": "live",
"consecutiveFailures": 0,
"threshold": 0,
"reasons": [
"string"
]
}
},
"readiness": {
"ready": false,
"missing": [
"string"
]
},
"group": {
"groupId": "string",
"groupName": "string",
"members": [
"string"
],
"suggestedCoveredBankKeys": [
"string"
]
},
"productionState": "test",
"sftpWizard": [
{
"id": "string",
"phase": "prepare",
"title": "string",
"short": "string",
"summary": "string",
"intro": "string",
"bullets": [
"string"
],
"notice": "string",
"wait": "string",
"steps": [
{
"type": "countries",
"title": "string",
"help": "string"
}
],
"stepCount": 0,
"unnumbered": false
}
],
"registryBankKey": {
"available": false,
"fingerprint": "string",
"validTo": "string",
"uid": "string",
"usable": false,
"unusableReason": "string"
}
}/connections/{id}/healthThe three health facts the channel dashboard shows: payments the bank accepted (a count over the last windowDays days, when the most recent acceptance landed, and how many were rejected), statements received (a count, plus the newest one's ISO document type and the format the bank actually sent), and the inbound endpoint (this connection's poll cadence and when it was last polled). It also carries inboundHealth (live, degraded or down, judged on one shared failure threshold, the same as every connection read) and the connection's own lifecycle connectionStatus, so a channel that has never been activated is not mistaken for one that is failing. Counts are windowed on the same event as the timestamp beside them: paymentsAccepted counts the acceptances and rejections that happened in the last windowDays days, and statementsReceived counts the statements that arrived in them. The last… timestamps are not windowed, so a channel that went quiet reports when it last worked, not null.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Channel health |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | Not found |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"connectionId": "string",
"bankKey": "string",
"environment": "test",
"connectionStatus": "setup",
"status": "live",
"inboundHealth": {
"status": "live",
"consecutiveFailures": 0,
"threshold": 0,
"reasons": [
"string"
]
},
"windowDays": 0,
"paymentsAccepted": {
"count": 0,
"lastAcceptedAt": "string",
"rejectedCount": 0
},
"statementsReceived": {
"count": 0,
"lastReceivedAt": "string",
"lastType": "string",
"lastSourceFormat": "string"
},
"endpoint": {
"pollIntervalSeconds": 0,
"lastPolledAt": "string"
}
}/connections/{id}/intraday-reportAsks the bank for its intraday account report (camt.052) on this connection now, and ingests what it returns like any other bank file: each report is a journal document (GET /journal/documents?type=camt.052, or GET /journal/documents/{id} for a documentId in the answer). Some banks generate this report each time it is requested (Danske Bank), so it is never fetched on the scheduled poll; this request is how one arrives. The bank decides which postings each report covers. complete is false when the bank refused a list, or a list or a download failed; ingested then holds what did arrive. A connection asks its bank at most once per 15 minutes, as Danske Bank asks its customers not to list files every few minutes. A request inside those 15 minutes does not reach the bank: it is answered with the last report again, reused: true, with the same fetchedAt. nextFetchAt is the earliest a request reaches the bank again.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | The reports this request ingested, or, inside the 15 minutes after the last fetch, that fetch's reports again |
| 400 | The bank endpoint is one the outbound address policy refuses (`validation_failed`; 422 on `BankConnector-Version: 2026-10-20`) No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | No such connection for this company (`connection_not_found`) |
| 409 | The connection is being polled right now; try again in a minute |
| 422 | No intraday report to fetch on this connection: it is not active, its bank offers none, or it is a test connection |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
| 502 | The bank could not be reached |
{
"connectionId": "string",
"ingested": [
{
"documentId": "string",
"type": "string",
"name": "string"
}
],
"skipped": 0,
"complete": false,
"fetchedAt": "string",
"reused": false,
"nextFetchAt": "string"
}/connections/{id}/test-filesThe test files this connection has to get through before it goes live, grouped by the country each belongs to. The countries are the connection's own claimed coverage (coveredBankKeys: a country belongs to exactly one connection per company and environment). Each country's file list comes from that country's bank (a bank with no domestic rail has fewer files), so the n of m counter's denominator is per country.
Each row's state is derived from the payment it describes. A pain.002 coming back advances that payment's status, and the row follows it. The exception is accepted-by-person: many banks send no pain.002 and confirm a test round by email, by phone, or by switching the channel on, so a named person marking a file accepted is a first-class path. accepted-by-bank and accepted-by-person are separate states and are never collapsed into one, because "the bank confirmed" and "a person said the bank confirmed" carry different weight if a test round is disputed. Where both exist, the bank's verdict decides state and the person's record is still served beside it.
items is one entry per country (total counts countries); fileCount / acceptedCount at the top level count files across all of them.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | The test files, grouped by country |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | Not found |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"connectionId": "string",
"bankKey": "string",
"environment": "test",
"items": [
{
"country": "string",
"countryName": "string",
"bankKeys": [
"string"
],
"files": [
{
"bankKey": "string",
"scenario": "string",
"label": "string",
"description": "string",
"amount": "string",
"currency": "string",
"paymentType": "string",
"format": "string",
"paymentCount": 0,
"state": "ready",
"accepted": false,
"payment": {},
"acceptance": {},
"withdrawal": {}
}
],
"acceptedCount": 0,
"fileCount": 0
}
],
"total": 0,
"acceptedCount": 0,
"fileCount": 0
}/connections/{id}/intro-emailThe email a company sends its bank to set the connection up: the bank's contact address where it is known (to, else null), a subject and a body asking the bank to set up the channel. The company's public keys reach the bank separately, never in this email. Nothing is sent: the company sends it itself, then marks it sent with POST /connections/{id}/intro-email-sent. Any company member or an API key.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Rendered email (subject + body) |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | Not found |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"email": {
"to": "string",
"subject": "string",
"body": "string"
}
}/connections/{id}/capability-sheetThe 2-page sheet attached to the agreement-request email: the payment schemes we offer through this bank in each country it covers, the pain.001 version and packaging we send, the files we need back, and how we connect. Every value comes from the bank registry and the payment-type catalog. A value we cannot source is omitted from the document and named in unknowns.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | PDF as base64, plus the fields the sheet could not state |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | Connection not found |
| 409 | The bank agreement pins a pain.001 version this bank does not support |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"pdfBase64": "string",
"filename": "string",
"unknowns": [
"string"
],
"pages": 0
}/banks/{bankKey}/setup-infoReturns the channel kind, requirements text, field definitions, known server and endpoint details, and SFTP wizard screens for a bank. A bank outside Danske's, Bank Connect's, Nordea Corporate Access's and OP's Web Services is gated: until BankConnector approves the organisation's request (POST /banks/{bankKey}/guide-access-request), or while it has no connection to the bank, the descriptor is locked (unlockOn: access-request, titles only) and carries accessRequest.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
bankKey BankKey | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
testChannel enum | query | no | Whether the bank offers the company a test channel: `no` leaves the test-payment steps out of the setup guide. Omit it for the guide before that question is answered. Any other value is ignored. |
channel enum | query | no | The channel to describe the setup for, when the company's connection to this bank was created on a channel other than the bank's declared one (a connection keeps its channel). The response's `channelType`, `kind` and `wizard` then describe that channel. Omit it for the bank's declared channel. Any other value is ignored. |
| Status | Description |
|---|---|
| 200 | Setup descriptor |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | Unknown bank |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"bankKey": "string",
"bankName": "string",
"countryCode": "string",
"painVersion": "string",
"painVersions": [
"string"
],
"batchBookingDefault": false,
"approverCodesDefaultOn": false,
"acceptedDebtorBics": [
"string"
],
"contactSupport": {
"message": "string"
},
"unsupported": {
"reason": "string"
},
"channelType": "string",
"channelDeclared": false,
"wsVariant": "string",
"kind": "string",
"requirementsText": "string",
"fields": [
{
"name": "string",
"label": "string",
"placeholder": "string",
"required": false,
"prefill": "string",
"help": "string",
"fullWidth": false,
"maxLength": 0,
"pattern": "string",
"setting": false,
"secret": false,
"options": [
{
"value": "string",
"label": "string"
}
],
"answer": false,
"perUserExempt": false,
"sink": "server-info"
}
],
"bankConnect": {
"title": "string",
"badge": {
"text": "string",
"ink": "string",
"bg": "string"
},
"subtitle": "string",
"countryName": "string",
"centralName": "string",
"connection": {
"label": "string",
"ink": "string",
"bg": "string"
},
"intro": "string",
"agreementIntro": "string",
"accountsText": "string",
"materialsIntro": "string",
"statementTypes": [
{
"code": "string",
"label": "string",
"shortLabel": "string",
"description": "string",
"recommended": false,
"recommendTag": "string",
"railLabel": "string",
"tooltip": "string"
}
],
"approvalIntro": "string",
"approval": [
{
"code": "string",
"label": "string",
"shortLabel": "string",
"description": "string",
"recommended": false,
"recommendTag": "string",
"railLabel": "string",
"tooltip": "string"
}
],
"deliverables": {
"heading": "string",
"intro": "string",
"items": [
{
"text": "string",
"tooltip": "string"
}
],
"footer": "string"
},
"activateTitle": "string",
"activateIntro": "string",
"activateCta": "string",
"activateNote": "string",
"activateReassurance": "string",
"goLivePending": "string",
"goLiveConnected": "string",
"statusUrl": "string"
},
"guide": {
"title": "string",
"badge": {
"text": "string",
"ink": "string",
"bg": "string"
},
"subtitle": "string",
"countryName": "string",
"centralName": "string",
"connection": {
"label": "string",
"ink": "string",
"bg": "string"
},
"intro": "string",
"agreementIntro": "string",
"accountsText": "string",
"materialsIntro": "string",
"statementTypes": [
{
"code": "string",
"label": "string",
"shortLabel": "string",
"description": "string",
"recommended": false,
"recommendTag": "string",
"railLabel": "string",
"tooltip": "string"
}
],
"approvalIntro": "string",
"approval": [
{
"code": "string",
"label": "string",
"shortLabel": "string",
"description": "string",
"recommended": false,
"recommendTag": "string",
"railLabel": "string",
"tooltip": "string"
}
],
"deliverables": {
"heading": "string",
"intro": "string",
"items": [
{
"text": "string",
"tooltip": "string"
}
],
"footer": "string"
},
"activateTitle": "string",
"activateIntro": "string",
"activateCta": "string",
"activateNote": "string",
"activateReassurance": "string",
"goLivePending": "string",
"goLiveConnected": "string",
"statusUrl": "string"
},
"guidedFlow": false,
"group": {
"id": "string",
"name": "string",
"exclusiveCoverage": false,
"members": [
{
"key": "string",
"bankName": "string",
"countryCode": "string",
"requiresApproverSsn": false
}
]
},
"brand": {
"policyId": "string",
"brandName": "string",
"countries": [
{
"bankKey": "string",
"bankName": "string",
"countryCode": "string",
"channelAlone": "ebics"
}
],
"rule": "string"
},
"wizard": {
"kind": "string",
"screens": [
{
"id": "string",
"phase": "prepare",
"title": "string",
"short": "string",
"summary": "string",
"intro": "string",
"bullets": [
"string"
],
"notice": "string",
"wait": "string",
"steps": [
{}
],
"stepCount": 0,
"unnumbered": false
}
],
"turns": [
{
"id": "prepare",
"title": "string",
"short": "string",
"intro": "string",
"bullets": [
"string"
],
"notice": "string",
"steps": [
{}
],
"stepCount": 0,
"gate": {
"unlocksAt": "created",
"awaitsBankAt": "created",
"resumesAt": "created",
"completesAt": "created",
"terminal": false
}
}
]
},
"knownServer": {
"gateway": "tietoevry",
"bankId": "string",
"host": "string",
"port": 0,
"uploadPath": "string",
"downloadPath": "string",
"testHost": "string",
"testPort": 0,
"testLoginDiffers": false,
"productionFirst": false,
"downloadHost": "string",
"usernameHint": "string",
"uploadTemplate": "string",
"downloadPattern": "string",
"inputs": [
{
"key": "string",
"label": "string",
"hint": "string"
}
],
"sshKeyBits": 0,
"pgpKeyBits": 0,
"dualAuth": false
},
"knownWebservice": {
"wsVariant": "danske",
"endpoint": "string",
"pkiEndpoint": "string",
"receiverId": "string",
"environment": "TEST",
"environmentEvidence": {
"basis": "string",
"source": "string",
"checkedOn": "string",
"detail": "string"
},
"registrationNumber": "string",
"setupFields": {
"add": [
{
"field": {},
"after": "string"
}
],
"remove": [
"string"
],
"copy": {}
}
},
"bankPgpPublicKeyKnown": false,
"sshKeyType": "string",
"keyAlgorithms": {
"pgp": "string",
"ssh": "string"
},
"supportsEnvironmentToggle": false,
"generalSettingsEnabled": false,
"ebics": {
"hostId": "string",
"url": "string",
"notes": [
"string"
],
"h3k": false,
"edsSupported": false,
"publishedKeyHashes": {
"authentication": "string",
"encryption": "string"
},
"publishedCertHashes": {
"authentication": "string",
"encryption": "string"
},
"keyLength": {
"bankMinimumBits": {
"value": 0,
"evidence": {
"basis": "string",
"source": "string",
"checkedOn": "string",
"detail": "string"
}
},
"generatedBits": 0
}
},
"operationalFacts": {
"cutOffs": {
"value": [
{
"scheme": "all",
"time": "string",
"timeZone": "Europe/Berlin",
"currency": "string"
}
],
"evidence": {
"basis": "string",
"source": "string",
"checkedOn": "string",
"detail": "string"
}
},
"statementFormats": {
"value": {
"offered": [
"mt940"
],
"notOffered": [
"mt940"
]
},
"evidence": {
"basis": "string",
"source": "string",
"checkedOn": "string",
"detail": "string"
}
},
"instantCreditTransfer": {
"value": {
"offered": false,
"maxAmount": {
"amount": 0,
"currency": "string"
},
"since": "string"
},
"evidence": {
"basis": "string",
"source": "string",
"checkedOn": "string",
"detail": "string"
}
},
"verificationOfPayee": {
"value": {
"offered": false,
"since": "string"
},
"evidence": {
"basis": "string",
"source": "string",
"checkedOn": "string",
"detail": "string"
}
},
"distributedSignature": {
"value": false,
"evidence": {
"basis": "string",
"source": "string",
"checkedOn": "string",
"detail": "string"
}
},
"onboardingStart": {
"value": "appointment",
"evidence": {
"basis": "string",
"source": "string",
"checkedOn": "string",
"detail": "string"
}
},
"onboardingDocuments": {
"value": [
{
"name": "string",
"role": "application",
"url": "string"
}
],
"evidence": {
"basis": "string",
"source": "string",
"checkedOn": "string",
"detail": "string"
}
},
"onboardingReturnRoutes": {
"value": [
{
"carries": "application",
"via": "email",
"address": "string",
"subject": "string"
}
],
"evidence": {
"basis": "string",
"source": "string",
"checkedOn": "string",
"detail": "string"
}
},
"onboardingContacts": {
"value": [
{
"label": "string",
"phones": [],
"email": "string",
"hours": "string"
}
],
"evidence": {
"basis": "string",
"source": "string",
"checkedOn": "string",
"detail": "string"
}
}
},
"identifiers": [
{
"slot": "agreementId",
"label": "string",
"placement": "header",
"writes": [
{
"at": "header",
"scheme": "string"
}
],
"mandatory": false,
"askedInSetup": false,
"formatHint": "string",
"evidence": {
"basis": "string",
"source": "string",
"checkedOn": "string",
"detail": "string"
}
}
],
"locked": false,
"unlockOn": "string",
"lockNote": "string",
"accessRequest": {
"status": "string"
}
}/banks/{bankKey}/sftp-namingFor a bank reached through TietoEvry's shared SFTP service: the server, the test and production logins, their folders and the name of a payment file, worked out from the agreement answers typed so far. An answer that is missing or malformed shows as a <placeholder>, and each malformed one is named in problems. complete is true once every answer is usable; the names are then the ones delivery uses. Nothing is saved. Gated like the bank's setup guide: an organisation that holds neither an approved access request nor a connection to the bank is refused with bank_guide_access_required.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
bankKey BankKey | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
code string | query | no | SFTP account code |
orgNo string | query | no | 9-digit organisation number |
custId string | query | no | Customer identifier (CUSTID) |
| Status | Description |
|---|---|
| 200 | The test and production logins, folders and file names |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | `bank_guide_access_required`: the organisation has no access to this bank's setup guide yet, or the credential's role, scope or grant does not allow this operation. |
| 404 | Unknown bank, or a bank whose server, logins and file names are not derived |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"complete": false,
"problems": [
"string"
],
"test": {
"host": "string",
"port": 0,
"username": "string",
"uploadFolder": "string",
"downloadFolder": "string",
"uploadFile": "string",
"uploadFilePath": "string"
},
"production": {
"host": "string",
"port": 0,
"username": "string",
"uploadFolder": "string",
"downloadFolder": "string",
"uploadFile": "string",
"uploadFilePath": "string"
}
}/banks/{bankKey}/settingsThe settings this company saved for one bank: its agreement ids, charge bearer, execution-date offset, the approval policy that governs the bank, the payment-file options and the setup in progress. settings is null when nothing is saved. Any company member or an API key; PATCH changes them.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
bankKey BankKey | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Bank settings (null if none saved) |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"settings": {
"companyId": "string",
"bankKey": "string",
"agreementId": "string",
"debtorAgreementId": "string",
"chargeBearer": "DEBT",
"executionDateOffsetDays": 0,
"approvalPolicyId": "string",
"painVersion": "pain.001.001.03",
"batchBooking": false,
"authorisationCode": "AUTH",
"authorisationProprietary": "string",
"noApproverCodesEnabled": false,
"noApproverAuthVendor": "string",
"noApproverAuthMethod": "string",
"deriveHybridAddress": false,
"fillDebtorAddressFromRegisteredAddress": false,
"memberBankCode": "string",
"selected": false,
"setupStep": "string",
"updatedAt": "string"
}
}/bank-settingsEvery bank's saved settings for this company, the same fields GET /banks/{bankKey}/settings returns for one, plus each SFTP setup's progress through the bank's steps. Any company member or an API key.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | All saved bank settings for this company |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"settings": [
{
"companyId": "string",
"bankKey": "string",
"agreementId": "string",
"debtorAgreementId": "string",
"chargeBearer": "DEBT",
"executionDateOffsetDays": 0,
"approvalPolicyId": "string",
"painVersion": "pain.001.001.03",
"batchBooking": false,
"authorisationCode": "AUTH",
"authorisationProprietary": "string",
"noApproverCodesEnabled": false,
"noApproverAuthVendor": "string",
"noApproverAuthMethod": "string",
"deriveHybridAddress": false,
"fillDebtorAddressFromRegisteredAddress": false,
"memberBankCode": "string",
"selected": false,
"setupStep": "string",
"updatedAt": "string",
"setupStepInfo": {
"index": 0,
"total": 0,
"label": "string"
}
}
]
}/connections/{id}/ebics-letterReturns the signed INI/HIA initialisation letter as a base64-encoded PDF. Print, sign, and send to the bank to complete EBICS subscriber activation. While a technical connection's re-key waits for the bank's answer, the letter is for the re-key's new keys.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
userId string | query | no | An approver's user id: returns that person's own letter, carrying their key hashes and EBICS User ID, in place of the connection's letter. |
type enum | query | no | `ini` or `hia` returns that one letter; omit it for both. Any other value is ignored. |
| Status | Description |
|---|---|
| 200 | PDF as base64 |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | No such connection for this company (`connection_not_found`) |
| 409 | `invalid_state`: the connection is not at a point that allows this request. A step before it is not done yet, or the connection is live, deactivated or on a channel this action does not apply to; the message says what to do first. Pin `BankConnector-Version: 2026-10-21` to get 409; earlier pins answer `validation_failed` (400, and 422 on `2026-10-20`). |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"pdfBase64": "string",
"filename": "string"
}/connections/{id}/ebics-subscribersEach approver enrolled on this connection with the EBICS User ID this bank issued, their activation state at the bank (keys-generated, ini-hia-sent or active), signature version and key length. An approver whose key set serves only another bank is not listed. Key material is never returned.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | The enrolled subscribers |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"subscribers": [
{
"userId": "string",
"ebicsUserId": "string",
"state": "keys-generated",
"signatureVersion": "A005",
"modulusLength": 0
}
]
}Journal
Submit payments + read the journal: the ERP integration surface (X-API-Key)
/journal/paymentsThe primary ERP integration call. Validates and converts the canonical payment for bankKey, records it in the journal, and routes it through approval and delivery. Send companyId, bankKey and payment; environment, validate, source, erpNotes, maker and idempotencyKey are optional. A session-less caller may name who initiated the payment in maker (reference, name, email), or send the address in the X-Initiator-Email header; it is recorded as claimed, never verified.
On host-to-host, payment.messageId is the payment's identity whether or not you also send an Idempotency-Key: the server keys the submission on msgid:<messageId>, so a new key never turns a messageId it has already seen into a second payment. An Idempotency-Key is mandatory for host-to-host only where there is no messageId to key on (a missing key then returns 400 idempotency_key_required). Send a unique key per logical payment so a network-timeout retry cannot double-submit. A retry with the same key and body is answered from the recorded payment (same record, xml, deliveryMode and status exactly as the first answer carried them) with the header Idempotency-Replayed: true. The per-run reports (approval, delivery, warnings, autoFilled, unknownFieldsDropped) are not guaranteed on a retry; read the document or its approval by journalNo for those. The same key with a different body → 409. A re-submission under a payment.messageId whose original payment is rejected, cancelled or rejected-by-approver is refused with 409 message_id_already_used; issue a new messageId. Requires authentication (X-API-Key for ERP integrations).
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
Idempotency-Key string | header | no | Unique per company per logical payment. On host-to-host, `payment.messageId` is the payment's identity whether or not this key is sent. Mandatory only where there is no `messageId` (else 400 `idempotency_key_required`). Same key + same body replays the first answer unchanged (same record, `xml`, `deliveryMode` and `status`, with `Idempotency-Replayed: true`). The per-run reports (`approval`, `delivery`, `warnings`, `autoFilled`, `unknownFieldsDropped`) are not guaranteed on a retry; read the document or its approval by `journalNo` for those. Same key + different body → 409. |
| Property | Type | Required | Description |
|---|---|---|---|
companyId | string | yes | The company this payment belongs to. |
bankKey | BankKey | yes | |
payment | PaymentInstruction | yes | |
environment | enum | no | Which connection delivers the payment. Defaults to production; pass "test" to dispatch over the bank's test channel. |
validate | boolean | no | Also XSD-validate the generated XML. |
idempotencyKey | string | no | Alternative to the Idempotency-Key header. |
maker | object | no | Who initiated this payment, for a session-less (API-key/ERP) caller only. A signed-in session's maker identity is always the session user and this is ignored for it. `reference` is resolved against this company's users; an unresolved reference is kept only as a display label and is not a verified maker for approval purposes. |
source | object | no | The ERP journal this payment came from, as your ERP names it, shown beside the payment's own id on the Payments list and the approval screen, e.g. "PAY-0031 · Payments 2026-09-28". Display only: it is not part of the payment and not part of the idempotency comparison, so a retry of the same payment under a renamed journal replays the original (it never answers 409). Optional; either field may be omitted. |
erpNotes | object[] | no | Facts your ERP records about this batch that an auditor must be able to read later. `payee-account-changed`: the payee's bank account differs from the one their last payment went to, and your ERP showed that warning to `shownTo` at `shownAt`. Each note is written into the payment's hash-chained `payment.converted` journal event and onto its approval request, where the approver sees it. `from` and `to` are recorded masked to their first two and last four characters. Never part of the payment: not in the bank file and not part of the idempotency comparison, so a retry with different notes replays the original answer. Optional. Text fields are trimmed, 1-140 characters, with no control, bidirectional-override or invisible (zero-width, LRM/RLM/ALM) characters; a note that breaks a rule is a 400. |
{
"companyId": "string",
"bankKey": "nordea-dk",
"payment": {
"schemaVersion": "1.0",
"messageId": "MSG-20260601-0001",
"creationDateTime": "2026-06-01T10:30:00Z",
"initiatingParty": {
"name": "Acme Corp ApS",
"organisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
},
"secondOrganisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
}
},
"payments": [
{
"paymentId": "PMT-0001",
"paymentType": "sepa",
"priority": "normal",
"chargeBearer": "DEBT",
"localInstrument": "string",
"localInstrumentProprietary": "string",
"categoryPurpose": "string",
"executionDate": "2026-06-04",
"debtor": {
"name": "Acme Corp ApS",
"country": "DK",
"postalAddress": {},
"account": {},
"agent": {},
"organisationId": {}
},
"serviceUserNumber": "123456",
"ultimateDebtor": {
"name": "Acme Subsidiary GmbH",
"country": "DE",
"postalAddress": {},
"organisationId": {}
},
"transactions": [
{}
]
}
]
},
"environment": "test",
"validate": false,
"idempotencyKey": "string",
"maker": {
"reference": "string",
"name": "string"
},
"source": {
"reference": "string",
"name": "string"
},
"erpNotes": [
{
"kind": "payee-account-changed",
"paymentRef": "string",
"from": "string",
"to": "string",
"shownTo": "string",
"shownAt": "2026-01-01T00:00:00Z"
}
]
}| Status | Description |
|---|---|
| 201 | Payment recorded and journaled. A replay of a prior idempotent submit also returns 201, with the response header `Idempotency-Replayed: true`. `warnings` is present only when the accepted payment raised non-blocking validation warnings; they never block a submit or change this status code. |
| 400 | `idempotency_key_required`: no Idempotency-Key (header or body) and no `payment.messageId` on a host-to-host submit No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 402 | Two billing gates share this status. Both apply to production submits only, never to a `test`-environment submit or to `POST /journal/payments/preview`. `trial_payments_exhausted`: this company has already sent its 10 free lifetime production payments and has no active package; `error.details` carries `productionPaymentsSent`, `freeLimit` and `packagesUrl` (GET /billing/packages for current prices). `billing_past_due`: an established account 30+ days past due on an unpaid invoice; `error.details` carries `pastDueSince` and `graceDays`. A payment already approved and in flight is never affected. |
| 403 | Scope mismatch (cross-tenant) |
| 409 | `request_in_progress`: the same key or `messageId` is still being processed; retry the same request shortly. `conflict`: the same key or `messageId` with a different body; never retry it. `message_id_already_used`: the payment previously submitted with this `payment.messageId` is terminal and unsuccessful (`rejected`, `cancelled` or `rejected-by-approver`), so this re-submission was not attempted. A `messageId` is used once: `details` carries `originalJournalNo`, `originalStatus` and `remedy: "new_message_id"`. Issue a new `messageId` for a new attempt; a new `Idempotency-Key` does not help. A re-submission while the original is still in flight (or already `executed`) is answered from the recorded payment, without a guarantee of the per-run reports. |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | Validation failed: all issues at once |
| 429 | Rate limited (production): see Retry-After |
{
"id": "doc_9f3a",
"journalNo": "OUT-000123",
"companyId": "comp_123",
"bankKey": "nordea-dk",
"environment": "production",
"direction": "outbound",
"type": "pain.001",
"status": "queued-for-delivery",
"summary": "SEPA · 1 payment",
"transactionCount": 1,
"totals": [
{
"currency": "EUR",
"amount": "1000.00"
}
],
"xml": "<?xml version=\"1.0\"?><Document xmlns=\"urn:iso:std:iso:20022:tech:xsd:pain.001.001.09\">…</Document>",
"deliveryMode": "host-to-host",
"createdAt": "2026-06-20T10:31:00Z",
"delivery": {
"status": "queued"
}
}curl -X POST https://sandbox.bankconnector.com/api/journal/payments \
-H 'X-API-Key: YOUR_KEY' \
-H 'BankConnector-Version: 2026-10-21' \
-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"}}}]}]}}'const res = await fetch("https://sandbox.bankconnector.com/api/journal/payments", {
method: "POST",
headers: {
"X-API-Key": "YOUR_KEY",
"BankConnector-Version": "2026-10-21",
"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();import requests
resp = requests.request(
"POST", "https://sandbox.bankconnector.com/api/journal/payments",
headers={"X-API-Key": "YOUR_KEY", "BankConnector-Version": "2026-10-21"},
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/previewRead-only. Runs the same settings-fill, validate and convert (+ XSD) pipeline as POST /journal/payments and returns the generated bank file. It 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.
X-API-Key header| Property | Type | Required | Description |
|---|---|---|---|
companyId | string | yes | The company this payment belongs to. |
bankKey | BankKey | yes | |
payment | PaymentInstruction | yes | |
environment | enum | no | Which connection delivers the payment. Defaults to production; pass "test" to dispatch over the bank's test channel. |
validate | boolean | no | Also XSD-validate the generated XML. |
idempotencyKey | string | no | Alternative to the Idempotency-Key header. |
maker | object | no | Who initiated this payment, for a session-less (API-key/ERP) caller only. A signed-in session's maker identity is always the session user and this is ignored for it. `reference` is resolved against this company's users; an unresolved reference is kept only as a display label and is not a verified maker for approval purposes. |
source | object | no | The ERP journal this payment came from, as your ERP names it, shown beside the payment's own id on the Payments list and the approval screen, e.g. "PAY-0031 · Payments 2026-09-28". Display only: it is not part of the payment and not part of the idempotency comparison, so a retry of the same payment under a renamed journal replays the original (it never answers 409). Optional; either field may be omitted. |
erpNotes | object[] | no | Facts your ERP records about this batch that an auditor must be able to read later. `payee-account-changed`: the payee's bank account differs from the one their last payment went to, and your ERP showed that warning to `shownTo` at `shownAt`. Each note is written into the payment's hash-chained `payment.converted` journal event and onto its approval request, where the approver sees it. `from` and `to` are recorded masked to their first two and last four characters. Never part of the payment: not in the bank file and not part of the idempotency comparison, so a retry with different notes replays the original answer. Optional. Text fields are trimmed, 1-140 characters, with no control, bidirectional-override or invisible (zero-width, LRM/RLM/ALM) characters; a note that breaks a rule is a 400. |
{
"companyId": "string",
"bankKey": "nordea-dk",
"payment": {
"schemaVersion": "1.0",
"messageId": "MSG-20260601-0001",
"creationDateTime": "2026-06-01T10:30:00Z",
"initiatingParty": {
"name": "Acme Corp ApS",
"organisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
},
"secondOrganisationId": {
"id": "DK12345678",
"scheme": "CUST",
"issuer": "string"
}
},
"payments": [
{
"paymentId": "PMT-0001",
"paymentType": "sepa",
"priority": "normal",
"chargeBearer": "DEBT",
"localInstrument": "string",
"localInstrumentProprietary": "string",
"categoryPurpose": "string",
"executionDate": "2026-06-04",
"debtor": {
"name": "Acme Corp ApS",
"country": "DK",
"postalAddress": {},
"account": {},
"agent": {},
"organisationId": {}
},
"serviceUserNumber": "123456",
"ultimateDebtor": {
"name": "Acme Subsidiary GmbH",
"country": "DE",
"postalAddress": {},
"organisationId": {}
},
"transactions": [
{}
]
}
]
},
"environment": "test",
"validate": false,
"idempotencyKey": "string",
"maker": {
"reference": "string",
"name": "string"
},
"source": {
"reference": "string",
"name": "string"
},
"erpNotes": [
{
"kind": "payee-account-changed",
"paymentRef": "string",
"from": "string",
"to": "string",
"shownTo": "string",
"shownAt": "2026-01-01T00:00:00Z"
}
]
}| Status | Description |
|---|---|
| 200 | Preview generated. Carries the same `warnings` a real submit would return (present only when the payment raised non-blocking warnings). |
| 400 | Missing fields No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | Validation / XSD failed |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"xml": "string",
"painVersion": "pain.001.001.03",
"preview": false,
"warnings": [
{
"code": "string",
"level": "generic",
"severity": "error",
"message": "string",
"path": "string",
"bank": "string",
"paymentType": "string"
}
],
"autoSelected": [
{
"paymentId": "string",
"selected": "string",
"confidence": "string",
"reason": "string"
}
],
"autoFilled": [
"string"
],
"unknownFieldsDropped": [
"string"
],
"whitelist": {
"applicable": false,
"outcome": "cleared",
"checks": [
{
"nip": "string",
"nrb": "string",
"date": "string",
"status": "listed",
"requestId": "string",
"error": "string"
}
],
"conversions": [
{
"nrb": "string",
"amount": "string",
"currency": "string",
"date": "string",
"rate": {
"table": "string",
"no": "string",
"effectiveDate": "string",
"mid": "string"
},
"amountPln": "string",
"reachesThreshold": false,
"error": "string"
}
]
},
"approval": {
"required": false
}
}/journal/payee-verificationsOpt-in. Builds the bank file exactly as POST /journal/payments/preview does and uploads it once as a verification-only file: at OP Web Services, FileType pain.001.001.09 VOP (or .03 VOP). The bank checks each payee's name against the account and pays nothing: no journal document is written, no approval runs, nothing is billed, and the file never counts as a sent payment. The bank may charge per check under your own agreement with it. Euro transfers only. The results arrive later as a payee-verification report: a file.received event whose document (type other, source format vop:pain.002.001.10) lists each payee by end-to-end id as match, close-match (with the name the payee's bank holds), no-match or not-possible; read it with GET /journal/documents/{id}. Requires authentication.
X-API-Key header| Property | Type | Required | Description |
|---|---|---|---|
companyId | string | yes | Your company id. |
bankKey | string | yes | The bank, e.g. `op-financial-group-fi`. |
payment | - | yes | The canonical payment instruction whose payees are checked. |
environment | enum | no | Which connection sends the file. Defaults to `production`. |
{
"companyId": "string",
"bankKey": "string",
"environment": "test"
}| Status | Description |
|---|---|
| 202 | The bank accepted the file for checking. `messageId` is the verification file's own MsgId (your instruction's `messageId` comes back as `requestMessageId`); the report names it as `originalMessageId`. |
| 400 | Missing fields, or the bank refused the file (`bank_rejected`) No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | No active connection to the bank in that environment (`connection_not_found`) |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | The connection's channel takes no verification-only file (`payee_verification_unsupported`), its test side is for development only (`production_only_channel`), a non-euro transfer, or validation / XSD failed |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
| 502 | The bank could not be reached, or its answer could not be verified (`bank_unavailable`); sending again is safe |
{
"messageId": "string",
"requestMessageId": "string",
"bankKey": "string",
"environment": "test",
"transactionCount": 0,
"endToEndIds": [
"string"
],
"reference": "string",
"painVersion": "string",
"xml": "string",
"sentAt": "2026-01-01T00:00:00Z"
}/journal/payments/batchFor a batch sent to POST /journal/payments whose answer never arrived (a timeout, a closed connection). Returns outcome: found with each payment's end-to-end id and state when the engine recorded the batch, purged when it recorded it and the document has since passed retention, processing while a submission of it holds its key without having recorded it, and not-found only when nothing holds the batch and no submission of it is running. Only not-found means the payments may be sent again, under a new messageId. Every outcome is a 200; the endpoint never answers 404 for an unknown batch, so a 404 from a proxy or a wrong path cannot be read as not-found. not-found speaks for the moment of the read: ask once your own send has ended, not while it may still be on its way. Tenant-scoped.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
messageId string | query | yes | The batch's `messageId`, as sent in the payment body to `POST /journal/payments`. The engine identifies a submission by its messageId whether or not an `Idempotency-Key` was sent, so a key that differs from the messageId does not find the batch. Surrounding spaces are ignored, as they are on submission. |
| Status | Description |
|---|---|
| 200 | What the engine holds for the batch |
| 400 | `messageId` is missing or longer than 35 characters (`invalid_parameter`). No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"messageId": "string",
"outcome": "found",
"documentId": "string",
"journalNo": "string",
"status": "string",
"environment": "test",
"bankKey": "string",
"createdAt": "2026-01-01T00:00:00Z",
"payments": [
{
"index": 0,
"payee": "string",
"amount": "string",
"currency": "string",
"endToEndId": "string",
"account": "string",
"executionDate": "string",
"recon": {
"state": "cleared",
"evidence": {
"source": "pain.002",
"summary": "string",
"date": "string",
"amount": "string",
"currency": "string",
"reason": "string",
"addtlInf": "string",
"documentId": "string"
}
}
}
],
"recon": {
"state": "cleared",
"evidence": {
"source": "pain.002",
"summary": "string",
"date": "string",
"amount": "string",
"currency": "string",
"reason": "string",
"addtlInf": "string",
"documentId": "string"
}
}
}/journal/ingestUpload a raw bank statement/status file; it is parsed to the one normalised shape and journaled. Requires authentication.
X-API-Key header| Property | Type | Required | Description |
|---|---|---|---|
companyId | string | yes | Your company id. |
bankKey | string | no | |
content | string | yes | Raw file contents |
environment | enum | no |
{
"companyId": "string",
"bankKey": "string",
"content": "string",
"environment": "test"
}| Status | Description |
|---|---|
| 200 | Dedupe hit: this content was already ingested. Returns the existing document unchanged; no events or status advances re-run. |
| 201 | File ingested and normalised: every statement block of the file was ingested. A file whose blocks did not all end the same way answers 207. |
| 207 | Partial: the file's statement blocks did not all end the same way (some ingested, some deduped, some quarantined as belonging to another company), or a file-level alert says the file was not whole (a per-account block that opened and never closed). The body is the 201's, the primary document plus the `ingest` block, and `ingest.statements` says what happened to every block. A camt.053 carries one block per account, so this status distinguishes "3 of 4 accounts imported" from a clean import. |
| 400 | The body is not JSON (`validation_failed`). Before `BankConnector-Version: 2026-10-20` a body that fails the route's schema is a 400 too. No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | Unrecognised / unparseable file, or `incomplete_statement`: the file is truncated, or states an amount or credit/debit indicator that could not be read. Nothing from the file is stored. In the second case `error.details.parseIssues` names each field. |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"id": "string",
"journalNo": "string",
"companyId": "string",
"bankKey": "string",
"environment": "test",
"direction": "inbound",
"type": "pain.001",
"sourceFormat": "string",
"summary": "string",
"status": "string",
"messageId": "string",
"importSource": "manual",
"transactionCount": 0,
"totals": [
{
"currency": "string",
"amount": "string"
}
],
"paymentType": "string",
"source": {
"reference": "string",
"name": "string"
},
"idempotencyKey": "string",
"collected": false,
"collectedAt": "string",
"createdAt": "string",
"expiresAt": "string",
"deduped": false,
"ingest": {
"statementCount": 0,
"ingested": 0,
"deduped": 0,
"quarantined": 0,
"partial": false,
"statements": [
{
"index": 0,
"kind": "ingested",
"documentId": "string",
"journalNo": "string",
"account": "string",
"message": "string"
}
],
"alerts": [
{
"kind": "string",
"message": "string",
"reason": "string",
"account": "string",
"unclosedBlockCount": 0,
"survivingStatementCount": 0
}
],
"statusUnchanged": {
"originalMessageId": "string",
"reportedStatus": "string",
"paymentStatus": "string",
"message": "string"
}
}
}/journal/documentsAn ERP polls GET /journal/list?uncollectedOnly=true for documents, or GET /journal/events?since= for events. Tenant-scoped list of payments + statements (metadata only, no payload). Filter by direction/type. Each row names its bank environment.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
direction enum | query | no | |
type enum | query | no | |
collected boolean | query | no | Filter by collection state: `false` = you have not collected it yet, `true` = already collected. This is the item's `collected` flag, not a `status` value. |
q string | query | no | Free-text search (case-insensitive, matched literally, `%` and `_` are not wildcards). Matches the document summary, messageId, journalNo, bankKey, status, type, and the rendered `<amount> <currency>` totals text. |
status string | query | no | One or more exact document statuses (e.g. `pending-approval`, `sent`, `executed`, `rejected`, `rejected-by-approver`), comma-separated for an or match (e.g. `sent,executed`). Exact match per value, not a search. |
amountMin string | query | no | Lower bound on any of the document's totals, inclusive. Decimal string, compared in exact minor units. Applied to the returned page by the service, not in the query, so with page size N you get the matching subset of that page; keep paging via `cursor` for the rest. A non-numeric value is ignored (no 400). |
amountMax string | query | no | Upper bound on any of the document's totals, inclusive. Same semantics as `amountMin`. |
dateFrom string | query | no | Only documents created on or after this day (`YYYY-MM-DD`, inclusive, server timezone). |
dateTo string | query | no | Only documents created on or before this day (`YYYY-MM-DD`, **inclusive**; the whole day is covered). |
accountId string | query | no | Only documents for one registered account. Its opaque id (`acct_…`), the same identifier `GET /accounts/statements` takes, not the IBAN. An id this company has no such account for returns an empty page, not an error. |
unresolvedOnly boolean | query | no | `true` leaves out an outbound batch whose every refused line was re-issued (`resendOf`) and the re-issue executed: the rows that carry `refusalsResolved`. The batch's `status` is not changed. Like `amountMin`, it is applied to the returned page, so keep paging via `cursor`. |
limit integer | query | no | Page size. Without it a page holds up to 1000 documents (500 on a filtered search). Every page carries `nextCursor`, `null` on the last one; pass it back as `cursor` for the next page. |
cursor string | query | no | Opaque cursor from a prior page's `nextCursor`. A malformed cursor is a 400 `invalid_cursor`. |
| Status | Description |
|---|---|
| 200 | Document summaries |
| 400 | A `direction`, `type` or `limit` that is not valid (`invalid_parameter`), or a malformed `cursor` (`invalid_cursor`). No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"id": "string",
"journalNo": "string",
"direction": "inbound",
"type": "pain.001",
"sourceFormat": "string",
"bankKey": "string",
"bankName": "string",
"environment": "test",
"summary": "string",
"status": "string",
"deliveryMode": "converted-only",
"importSource": "manual",
"transactionCount": 0,
"totals": [
{
"currency": "string",
"amount": "string"
}
],
"paymentType": "string",
"payeeName": "string",
"source": {
"reference": "string",
"name": "string"
},
"accountIban": "string",
"accountCurrency": "string",
"refusalsResolved": 0,
"collected": false,
"collectedAt": "string",
"createdAt": "string"
}
],
"nextCursor": "string",
"total": 0
}/journal/exportBulk extraction of full pain.001/camt payloads (account numbers and amounts), optionally filtered within a statement. Admin-only for human sessions; the ERP API key is the intended consumer. Every returned document is marked collected in the same database transaction that reads it. Safe to retry: a repeated call with the same journalNos returns every matching document again regardless of its collected state (wasAlreadyCollected tells you which), so a response your client never received (timeout, dropped connection) does not lose the data. If you track your backlog only via GET /journal/list?uncollectedOnly=true, keep the journalNos from a call until you confirm you received its response: they are marked collected, and so drop off that list, whether or not the response arrived. Send at most 200 journalNos per call (400 too_many_journal_nos above that). The optional filter narrows a camt.053 statement's transactions to the matching ones and leaves the statement header whole: direction (credit or debit), txCodeDomain and txCodeFamily (ISO bank transaction code, case-insensitive prefix), counterpartyName (case-insensitive substring), endToEndId (exact), amountMin and amountMax (inclusive decimal strings), and fromDate and toDate (booking dates, YYYY-MM-DD, inclusive). Any other document type is returned whole.
X-API-Key header| Property | Type | Required | Description |
|---|---|---|---|
companyId | string | yes | Your company id. |
journalNos | string[] | yes | |
filter | - | no |
{
"companyId": "string",
"journalNos": [
"string"
]
}| Status | Description |
|---|---|
| 200 | Exported documents + any notFound journal numbers |
| 207 | Partial hit: some of the requested journalNos exist and were exported, but at least one did not: check `notFound` for which. Same body as 200. Branch on the status code: a 200-only client would treat this as full success and miss the unexported documents. |
| 400 | `journal_nos_required` (no `journalNos`) or `too_many_journal_nos` (more than 200 in one call) No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Admin required (human session) |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | The body is JSON but fails the route's schema, or names a field the route does not read (`validation_failed`, one entry per problem in `details.issues`). Pin `BankConnector-Version: 2026-10-20` to get 422; earlier pins answer 400. |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"exported": [
{
"journalNo": "string",
"documentId": "string",
"direction": "inbound",
"type": "pain.001",
"sourceFormat": "string",
"bankKey": "string",
"createdAt": "string",
"wasAlreadyCollected": false,
"transactionCount": 0
}
],
"notFound": [
"string"
]
}/journal/listTenant-scoped, newest-first list of journal documents with a stable opaque cursor for forward pagination. Metadata only, no payload. For ERP polling, pass uncollectedOnly=true to fetch only documents you have not collected yet, then mark them collected via POST /journal/export. total counts all documents; uncollected counts how many are still uncollected. Each item carries collected (boolean), collectedAt and environment; an ERP that books statements skips the test ones, which a bank's test system can send for a real account.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | Your company id. |
cursor string | query | no | Opaque cursor from a prior page's `nextCursor`. |
limit integer | query | no | Page size: default 500, max 1000 (larger than the interactive /journal/documents feed, for bulk ERP pulls). |
direction enum | query | no | |
type enum | query | no | |
uncollectedOnly boolean | query | no | When `true`, returns only documents not yet collected (the standard poll-then-collect loop). |
| Status | Description |
|---|---|
| 200 | A page of documents + counts + nextCursor |
| 400 | A `direction`, `type` or `limit` that is not valid (`invalid_parameter`), or a malformed `cursor` (`invalid_cursor`). No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"total": 42,
"uncollected": 3,
"nextCursor": null,
"items": [
{
"journalNo": "OUT-000123",
"direction": "outbound",
"type": "pain.001",
"sourceFormat": "pain.001.001.09",
"bankKey": "nordea-dk",
"environment": "production",
"summary": "SEPA · 1 payment",
"status": "queued-for-delivery",
"collected": false,
"collectedAt": null,
"createdAt": "2026-06-20T10:31:00Z",
"expiresAt": null
}
]
}/journal/documents/{id}Returns the document including its canonical/normalised payload and, for seeded/generated docs, the raw xml for download. Tenant-scoped. On a production server, a statement (camt.052/053/054) received from a bank's test environment is a 404, here and on /file, /export.csv and /export.camt053.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Document + payload |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | Not found (or not in this tenant's scope) |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"id": "string",
"journalNo": "string",
"companyId": "string",
"bankKey": "string",
"environment": "test",
"direction": "inbound",
"type": "pain.001",
"sourceFormat": "string",
"summary": "string",
"status": "string",
"messageId": "string",
"importSource": "manual",
"transactionCount": 0,
"totals": [
{
"currency": "string",
"amount": "string"
}
],
"paymentType": "string",
"source": {
"reference": "string",
"name": "string"
},
"idempotencyKey": "string",
"collected": false,
"collectedAt": "string",
"createdAt": "string",
"expiresAt": "string",
"inputTrace": {
"filled": [
{
"path": "string",
"source": "bank-settings",
"setting": "string"
}
],
"warnings": [
{
"code": "string",
"level": "generic",
"severity": "error",
"message": "string",
"path": "string",
"bank": "string",
"paymentType": "string"
}
],
"unknownFieldsDropped": [
"string"
]
},
"entryRecon": [
{
"index": 0,
"state": "matched",
"journalNo": "string",
"lineCount": 0,
"line": {
"number": 0,
"payee": "string"
},
"source": {
"reference": "string",
"name": "string"
}
}
],
"payeeVerification": {
"reportDocumentId": "string",
"final": false,
"reportedAt": "string",
"notices": [
{
"statusCode": "string",
"text": "string"
}
],
"transactions": [
{
"endToEndId": "string",
"result": "match",
"statusCode": "string",
"byOmission": false,
"suggestedName": "string",
"reasonCode": "string",
"note": "string"
}
],
"unreadEntries": 0
},
"csvSpecification": [
{
"index": 0,
"kind": "items",
"itemCount": 0,
"notification": {
"id": "string",
"journalNo": "string"
}
}
]
}/journal/documents/{id}/paymentsThe payments inside an outbound pain.001 batch: each line's payee, amount, end-to-end id and reconciliation state, with the batch's approval request and its decision history. Tenant-scoped.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Per-line payments (payee, amount, endToEndId, status) |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | `not_found`: no payment batch for this document (out of scope, or not an outbound pain.001) |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"documentId": "string",
"journalNo": "string",
"messageId": "string",
"status": "string",
"deliveryMode": "converted-only",
"bankKey": "string",
"bankName": "string",
"createdAt": "string",
"payments": [
{
"index": 0,
"payee": "string",
"amount": "string",
"currency": "string",
"endToEndId": "string",
"account": "string",
"executionDate": "string",
"recon": {
"state": "cleared",
"evidence": {
"source": "pain.002",
"summary": "string",
"date": "string",
"amount": "string",
"currency": "string",
"reason": "string",
"addtlInf": "string",
"documentId": "string"
}
}
}
],
"approval": {
"id": "string",
"status": "pending",
"paymentRef": "string",
"payeeName": "string",
"currency": "string",
"currencies": [
"string"
],
"maxTransaction": "string",
"journalTotal": "string",
"requiredApprovals": 0,
"requireOneFromEachGroup": false,
"orderedApproval": false,
"groupCount": 0,
"approvals": [
{
"userName": "string",
"at": "string",
"authorityLapsed": "deactivated"
}
],
"rejections": [
{
"userName": "string",
"at": "string",
"reason": "string"
}
],
"canApprove": false,
"blockReason": "string",
"twoFactorRequired": false
},
"recon": {
"state": "cleared",
"evidence": {
"source": "pain.002",
"summary": "string",
"date": "string",
"amount": "string",
"currency": "string",
"reason": "string",
"addtlInf": "string",
"documentId": "string"
}
},
"refusals": {
"refused": 0,
"resolved": 0,
"resends": [
{
"endToEndId": "string",
"documentId": "string",
"journalNo": "string",
"resendEndToEndId": "string",
"state": "cleared",
"resolved": false
}
]
},
"stepTimes": {
"submittedAt": "string",
"sentAt": "string"
},
"awaitingBankValidation": {
"portal": "string"
}
}/journal/documents/{id}/fileReturns the stored file as an application/xml attachment. For an outbound payment that is the pain.001 we generated (?version=approved returns the submit-time file instead of the delivered one). For an inbound camt.052/053/054 it is the bank's original file, byte for byte as it arrived.
The inbound statement file is retained for 30 days by default (configurable per deployment) and is then destroyed; the statement itself, its balances and the audit trail are kept for the full document retention period. A request after that answers 404 with error.code raw_file_expired and a message naming the expiry date. That is the retention policy working, not a fault: treat the statement JSON from GET /journal/documents/{id} as the durable record, and re-request the file from the bank if you need the original bytes. A 404 with not_found means the document isn't in scope or stored no file at all. Downloading does not mark the document collected.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
version enum | query | no | `approved` returns the file as it stood when the payment was submitted, what the approver reviewed, for a payment whose delivered file was rebuilt at release to carry approver codes. Omit it for the file that went to the bank; for every other document the two are the same file. |
| Status | Description |
|---|---|
| 200 | The stored XML (attachment). The generated pain.001 outbound, the bank's original camt inbound |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | No downloadable file for this document: `not_found` (out of scope, or no file was stored) or `raw_file_expired` (an inbound statement whose raw bank file passed its retention window) |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
/journal/documents/{id}/export.csvReturns one camt.053 statement as the versioned bankconnector-statement-v1 CSV profile: UTF-8 with BOM, ; delimited, . decimal, ISO dates, one record per transaction, debit amounts negative. The row 1 names the columns, fixed by the profile version. There is no format parameter: other clients read the JSON of GET /journal/documents/{id}. 404 if the document isn't in scope or isn't a camt.053. camt.052 and camt.054 describe movements the day's camt.053 books again, so exporting them could post the same transaction twice. One exception, never beside the total: on a Finnish account, a combined reference-payment entry is carried as the lines of the camt.054 that itemises it when they sum to it exactly, flagged item in the last column, specification. Which entries are itemised is decided at the statement's first export and repeated by every later one. 409 statement_specification_expired when an itemised entry's camt.054 has passed its retention period. Downloading does not mark the document collected.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | The statement as CSV (attachment) |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | No bank statement to export for this document |
| 409 | `statement_specification_expired`: the statement's CSV carries a Finnish combined entry as the items of a camt.054 that has passed its retention period, so the file can no longer be reproduced line for line |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
/journal/documents/{id}/export.camt053Returns one stored bank statement as an ISO 20022 camt.053.001.02 file, the version ERP statement importers read, whatever format the bank sent it in (camt.053.001.08, MT940, …). It is rendered from the statement the journal keeps for the document's whole retention period, so it works after the bank's original file has expired (/file then answers raw_file_expired). The same document always gives the same bytes, so an ERP's duplicate detection keeps working across downloads.
The file carries the account, statement id, sequence, period and balances, and per entry the amount, direction, status, booking and value dates, the bank's references, the bank transaction code, and per transaction its references, parties and accounts, remittance (unstructured and structured), charges, currency exchange, purpose and return reason. An entry the bank booked as one batch is written as one Ntry with its transactions when the stored statement proves the grouping; otherwise each transaction is its own Ntry. A text longer than the ISO element allows is cut to fit; the JSON of GET /journal/documents/{id} keeps it whole. A pending or future-dated entry is written PDNG and one with an unrecognised status INFO, never BOOK.
404 if the document isn't in scope or isn't a camt.053: camt.052 and camt.054 describe movements the day's camt.053 books again, so importing them as a statement could post the same transaction twice. Downloading does not mark the document collected.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | The statement as camt.053.001.02 (attachment) |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | No bank statement to export for this document |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
/journal/payment-typesA tally of your outbound payments grouped by payment type, weighted by transaction count.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Per-type counts + total |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"counts": {},
"total": 0,
"sampled": 0
}/journal/audit/verifyRe-walks every event in the company's activity log (the rows GET /journal/events serves) and checks each event's hash and its link to the one before. Read-only: it never writes to the log.
A broken chain is answered 200, not an error. ok: false with brokenAt naming the first event that failed (index is its position in the walk, oldest first; reason is content-altered, broken-link or retired-key). Read ok, never the status code alone, to decide whether the chain is intact. length is how many events were checked. genesisAnchored: false means the oldest events are gone: purgeVerified then says whether a signed retention checkpoint proves that was a legitimate purge (true) or not (false); it is null when nothing was truncated.
The walk covers the company's whole history, so its cost grows with the log; it shares the per-caller rate limit of the other heavy journal reads.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | The chain's verdict: intact (`ok: true`) or broken (`ok: false` + `brokenAt`) |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required |
| 403 | A request naming another company. |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"ok": false,
"length": 0,
"genesisAnchored": false,
"brokenAt": {
"index": 0,
"eventId": "string",
"reason": "content-altered"
},
"purgeVerified": false
}/journal/documents/{id}/raw-pain002Returns the inbound pain.002 exactly as the bank sent it, as an application/xml attachment. This keeps the bank's own account of a rejection for the whole life of the document: unlike the raw camt statement files it is not removed after a retention window. 404 not_found when the document is not in scope, is not an inbound pain.002, or stored no file. Downloading does not mark the document collected.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | The stored pain.002 XML, as the bank sent it (attachment) |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | No stored raw pain.002 for this document |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
/journal/reconciliationReturns each outbound payment's state (cleared, rejected, cancelled, pending, not-sent or unconfirmed) with the matching evidence, by matching inbound pain.002 status reports and camt.053/054 statement entries 1:1. Keyset-paginated over the outbound payments (default 500, max 1000): a non-null nextCursor means more payments exist. Pass it back as ?cursor=. A payment an approver rejected (status: rejected-by-approver) also carries rejection: who rejected it, when, and the reason they gave.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
limit integer | query | no | Page size over the outbound payments. |
cursor string | query | no | Opaque keyset cursor from a previous response's `nextCursor`. |
| Status | Description |
|---|---|
| 200 | Per-payment reconciliation + evidence |
| 400 | A `limit` that is not valid (`invalid_parameter`). No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"documentId": "string",
"journalNo": "string",
"messageId": "string",
"bankKey": "string",
"summary": "string",
"totals": [
{
"currency": "string",
"amount": "string"
}
],
"status": "string",
"createdAt": "2026-01-01T00:00:00Z",
"state": "cleared",
"evidence": {
"source": "pain.002",
"summary": "string",
"date": "string",
"amount": "string",
"currency": "string",
"reason": "string",
"addtlInf": "string",
"documentId": "string"
},
"rejection": {
"reason": "string",
"rejectedBy": {
"name": "string"
},
"rejectedAt": "2026-01-01T00:00:00Z"
}
}
],
"nextCursor": "string"
}/journal/eventsKeyset-paginated newest-first (default and maximum page size 5000): a non-null nextCursor means more events exist. Pass it back as ?cursor=.
?since=<seq> is a second paging mode for a poller with no public endpoint of its own (webhooks unreachable). It walks the same rows oldest-first by the seq every item carries, so since=<the last seq you saved> resumes where you left off, even after a crash, a restart or a week offline. ?since= and ?cursor= are mutually exclusive (400 if both are given).
?connectionId=<id> narrows the feed to one bank channel: that connection's own lifecycle events plus every event recorded against a bank the connection serves (a shared connection covers a whole bank group), in this connection's environment. A company can hold a test and a production connection for the same bank, so an event stamped with the other environment is excluded. Events with no environment appear on both. It composes with either paging mode.
?from=/?to= bound the feed by event time, inclusive at both ends. Both are instants, not calendar days, because the server does not know your timezone. They combine with cursor, since and connectionId, and a from later than to is a 400.
?notify= narrows the feed to events that concern a person. action is what somebody in the company must do something about (an approval waiting, a delivery that failed, a certificate about to expire); watch is what has consequences nobody there can clear (a statement gap, a double debit, a revoked integration); any is both. Sign-ins and the rest of the audit record are in none of them. An unrecognised value is a 400. It combines with limit, cursor, since, connectionId, from and to.
A file.received event's metadata carries the documentId of the file, its type (camt.053, pain.002, …) and, for a statement, accountIban: the account masked to its first two and last four characters, as on every statement event.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
limit integer | query | no | |
cursor string | query | no | Opaque keyset cursor from a previous response's `nextCursor`. Mutually exclusive with `since`. |
connectionId string | query | no | Return only this bank channel's events: its own events plus its banks' events in this connection's environment (see the endpoint description). 404 `connection_not_found` if the id is not one of your connections. Combines with `limit`, `cursor` and `since`. |
since integer | query | no | Resume the feed oldest-first after this `seq` (0 starts from the beginning). Pass the highest `seq` you have processed; a non-null `nextCursor` in the response is the next `since` value, and more events are already waiting. A null `nextCursor` means you are caught up. Mutually exclusive with `cursor`. |
from string | query | no | Return only events at or after this instant (inclusive). An ISO-8601 timestamp, not a calendar day, because the server does not know your timezone. Combines with `limit`, `cursor`, `since` and `connectionId`. 400 `invalid_parameter` if `from` is later than `to`. |
to string | query | no | Return only events at or before this instant (inclusive). See `from`. |
notify enum | query | no | Return only events that concern a person: `action` (somebody must do something), `watch` (consequences nobody there can clear) or `any` (both). The in-app notification bell reads this. 400 `invalid_parameter` on any other value. |
lang enum | query | no | Language of each item's `display` text. `display` is derived at read time and the stored `message` is never changed. |
| Status | Description |
|---|---|
| 200 | Events newest-first |
| 400 | A `limit` that is not valid (`invalid_parameter`). No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | `connection_not_found`: `connectionId` is not one of your connections |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"id": "string",
"companyId": "string",
"type": "string",
"message": "string",
"actorUserId": "string",
"actorName": "string",
"actorFirmName": "string",
"metadata": {
"changes": [
{}
]
},
"createdAt": "string",
"expiresAt": "string",
"prevHash": "string",
"hash": "string",
"chainFormat": 0,
"seq": 0,
"display": {
"event": "string",
"text": "string",
"reference": "string"
}
}
],
"nextCursor": "string"
}/inbound/payment-statusParses a raw pain.002 into the one normalised status shape and returns it. Nothing is stored, matched to a payment or advanced, and no company is involved. To record a status report against the payments it answers, send the file to POST /journal/ingest; GET /journal/reconciliation then shows each payment's state. The body is the raw XML, not JSON.
X-API-Key header| Status | Description |
|---|---|
| 200 | The normalised payment status |
| 400 | Empty body (`empty_body`), or not a pain.002 (`unrecognised_format`) |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | The file could not be parsed (`parse_failed`) |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"messageId": "string",
"createdAt": "string",
"originalMessageId": "string",
"sourceFormat": "pain.002.001.02",
"groupStatus": "accepted",
"groupStatusCode": "string",
"groupReasons": [
{
"code": "string",
"description": "string",
"originator": "string"
}
],
"originalTxCount": 0,
"paymentGroups": [
{
"paymentInfoId": "string",
"status": "accepted",
"statusCode": "string",
"reasons": [
{
"code": "string",
"description": "string",
"originator": "string"
}
],
"transactions": [
{
"endToEndId": "string",
"paymentInfoId": "string",
"instructionId": "string",
"originalAmount": "string",
"currency": "string",
"status": "accepted",
"statusCode": "string",
"reasons": [],
"bankRef": "string",
"acctSvcrRef": "string",
"stsId": "string"
}
]
}
],
"rejectedTransactions": [
{
"endToEndId": "string",
"paymentInfoId": "string",
"instructionId": "string",
"originalAmount": "string",
"currency": "string",
"status": "accepted",
"statusCode": "string",
"reasons": [
{
"code": "string",
"description": "string",
"originator": "string"
}
],
"bankRef": "string",
"acctSvcrRef": "string",
"stsId": "string"
}
],
"acceptedTransactions": [
{
"endToEndId": "string",
"paymentInfoId": "string",
"instructionId": "string",
"originalAmount": "string",
"currency": "string",
"status": "accepted",
"statusCode": "string",
"reasons": [
{
"code": "string",
"description": "string",
"originator": "string"
}
],
"bankRef": "string",
"acctSvcrRef": "string",
"stsId": "string"
}
]
}/inbound/statementParses the raw file body into normalised JSON without storing it (use POST /journal/ingest to store). A camt.053 may carry several <Stmt> (one per account); all are returned in the items envelope. When this exact file is already in the journal, alreadyImported names the document it was filed as. POST /journal/ingest deduplicates on the same key.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Normalised statement(s) |
| 400 | Empty body or unrecognised format No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | Unparseable file, or `incomplete_statement`: the file is truncated, or states an amount or credit/debit indicator that could not be read. In the second case `error.details.parseIssues` names each field, and POST /journal/ingest refuses the same file. |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"messageId": "string",
"createdAt": "string",
"sequenceNumber": "string",
"statementId": "string",
"sourceFormat": "string",
"accountIban": "string",
"accountOther": "string",
"accountCurrency": "string",
"accountOwner": "string",
"fromDate": "string",
"toDate": "string",
"entrySummary": {
"totalCount": 0,
"totalSum": "string",
"totalDirection": "credit",
"creditCount": 0,
"creditSum": "string",
"debitCount": 0,
"debitSum": "string"
},
"balances": [
{
"type": "string",
"amount": "string",
"currency": "string",
"direction": "credit",
"date": "string",
"creditLineIncluded": false,
"creditLineAmount": "string",
"availability": []
}
],
"closingBalance": {
"type": "string",
"amount": "string",
"currency": "string",
"direction": "credit",
"date": "string",
"creditLineIncluded": false,
"creditLineAmount": "string",
"availability": [
{}
]
},
"openingBalance": {
"type": "string",
"amount": "string",
"currency": "string",
"direction": "credit",
"date": "string",
"creditLineIncluded": false,
"creditLineAmount": "string",
"availability": [
{}
]
},
"transactions": [
{
"bankRef": "string",
"endToEndId": "string",
"instructionId": "string",
"amount": "string",
"currency": "string",
"direction": "credit",
"status": "booked",
"bookingDate": "string",
"valueDate": "string",
"paymentDate": "string",
"counterpartyName": "string",
"counterpartyIban": "string",
"counterpartyAccount": "string",
"counterpartyAccountScheme": "string",
"counterpartyRegNo": "string",
"counterpartyAccountNo": "string",
"counterpartyBic": "string",
"counterpartyCountry": "string",
"counterpartyAddress": "string",
"remittance": "string",
"remittanceLines": [],
"creditorReference": "string",
"creditorReferenceType": "string",
"creditorReferenceScheme": "string",
"fik": {},
"creditorReferences": [],
"referredDocuments": [],
"txCodeDomain": "string",
"txCodeFamily": "string",
"txCodeProprietary": "string",
"txCodeProprietaryIssuer": "string",
"textKeySupplement": "string",
"entryTxCodeProprietary": "string",
"entryTxCodeProprietaryIssuer": "string",
"originalCurrency": "string",
"originalAmount": "string",
"instructedAmount": "string",
"chargeAmount": "string",
"chargeCurrency": "string",
"entryInfo": "string",
"transactionInfo": "string",
"txCodeSubFamily": "string",
"paymentInfoId": "string",
"originalMessageId": "string",
"batchTransactionCount": 0,
"ultimateDebtorName": "string",
"ultimateCreditorName": "string",
"exchangeRate": "string",
"exchangeSourceCurrency": "string",
"exchangeTargetCurrency": "string",
"counterValueAmount": "string",
"counterValueCurrency": "string",
"purposeCode": "string",
"mandateId": "string",
"chargeBearer": "string",
"clearingSystemReference": "string",
"proprietaryReference": "string",
"counterpartyOrganisationId": "string",
"counterpartyOrganisationIdScheme": "string",
"counterpartyPersonId": "string",
"entryReference": "string",
"isReversal": false,
"returnReasonCode": "string",
"returnReasonInfo": "string",
"originalBankRef": "string",
"interbankSettlementDate": "string",
"entryBankRef": "string",
"additionalInfoMessageId": "string",
"counterpartyBankCode": "string",
"isBatchBooking": false,
"rail": "bacs",
"fasterPaymentId": "string",
"gvc": "string",
"primanota": "string"
}
],
"droppedCurrencies": [
"string"
]
}
],
"alreadyImported": {
"journalNo": "string",
"importedAt": "string"
}
}Approvals
Maker-checker approval policies + the approval queue: reading them (API key or session); deciding and changing them (signed-in Admin or Approver)
/approvals/pendingFor a signed-in user this is a PERSONAL queue: only the requests waiting on that user's own signature: they are a designated approver for it, they did not raise it under maker-checker, and they have not already signed it. Two approvers in one company therefore get different lists. An API-key caller has no user and receives the company's whole list, as does any caller asking for the decided trail (?status=approved / ?status=rejected).
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
status enum | query | no | `pending` (the default) is the queue described above; `approved` or `rejected` is the company's decided trail. |
cursor string | query | no | The previous page's `nextCursor`: the page continues after that request. |
after string | query | no | Deprecated alias of `cursor`; `cursor` wins when both are sent. |
limit integer | query | no | Page size, at most 500. |
| Status | Description |
|---|---|
| 200 | Pending approval requests |
| 400 | A `status` or `limit` that is not valid (`invalid_parameter`), or a malformed `cursor` (`invalid_cursor`). No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"id": "string",
"companyId": "string",
"documentId": "string",
"paymentRef": "string",
"source": {
"reference": "string",
"name": "string"
},
"erpNotes": [
{
"kind": "payee-account-changed",
"paymentRef": "string",
"from": "string",
"to": "string",
"shownTo": "string",
"shownAt": "string"
}
],
"makerUserId": "string",
"makerName": "string",
"makerClaimedEmail": "string",
"maxTransaction": "string",
"journalTotal": "string",
"currency": "string",
"currencies": [
"string"
],
"payeeName": "string",
"payeeCount": 0,
"fundingAccount": {
"iban": "string",
"accountOther": "string",
"bankKey": "string",
"accounts": [
{}
]
},
"policyVersionId": "string",
"requiredApprovals": 0,
"allowSelfApproval": false,
"approverLimits": {},
"requireOneFromEachGroup": false,
"orderedApproval": false,
"groups": [
{
"id": "string",
"name": "string",
"userIds": []
}
],
"approverUserIds": [
"string"
],
"approvals": [
{
"userId": "string",
"userName": "string",
"at": "string",
"identity": {},
"ebicsEs": {},
"channel": "string",
"authorityLapsed": "deactivated"
}
],
"rejections": [
{
"userId": "string",
"userName": "string",
"at": "string",
"identity": {},
"ebicsEs": {},
"reason": "string",
"channel": "string"
}
],
"status": "pending",
"createdAt": "string",
"updatedAt": "string",
"evaluation": {
"satisfied": false,
"missing": [
"string"
],
"approverCount": 0,
"nextSigner": {
"name": "string",
"step": 0,
"of": 0
},
"uncompletableReason": "string"
},
"twoFactorRequired": false
}
],
"nextCursor": "string"
}/approvals/{id}The request, its evaluation, its payment lines (payee, account, amount, currency, execution date, remittance), per-currency totals and the contentDigest a decision from the ERP add-on must echo. erpDecision says whether the add-on may decide it.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | The request |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | No such request in this organisation (or not one that names this ERP sign-in) |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"request": {
"id": "string",
"companyId": "string",
"documentId": "string",
"paymentRef": "string",
"source": {
"reference": "string",
"name": "string"
},
"erpNotes": [
{
"kind": "payee-account-changed",
"paymentRef": "string",
"from": "string",
"to": "string",
"shownTo": "string",
"shownAt": "string"
}
],
"makerUserId": "string",
"makerName": "string",
"makerClaimedEmail": "string",
"maxTransaction": "string",
"journalTotal": "string",
"currency": "string",
"currencies": [
"string"
],
"payeeName": "string",
"payeeCount": 0,
"fundingAccount": {
"iban": "string",
"accountOther": "string",
"bankKey": "string",
"accounts": [
{
"iban": "string",
"accountOther": "string",
"totals": []
}
]
},
"policyVersionId": "string",
"requiredApprovals": 0,
"allowSelfApproval": false,
"approverLimits": {},
"requireOneFromEachGroup": false,
"orderedApproval": false,
"groups": [
{
"id": "string",
"name": "string",
"userIds": [
"string"
]
}
],
"approverUserIds": [
"string"
],
"approvals": [
{
"userId": "string",
"userName": "string",
"at": "string",
"identity": {
"approverUserId": "string",
"approverName": "string",
"approvedAt": "string",
"authMethod": "totp",
"ssnFirst6": "string",
"approvalReference": "string"
},
"ebicsEs": {
"signatureVersion": "A005",
"signatureB64": "string",
"ebicsUserId": "string",
"orderSha256B64": "string"
},
"channel": "string",
"authorityLapsed": "deactivated"
}
],
"rejections": [
{
"userId": "string",
"userName": "string",
"at": "string",
"identity": {
"approverUserId": "string",
"approverName": "string",
"approvedAt": "string",
"authMethod": "totp",
"ssnFirst6": "string",
"approvalReference": "string"
},
"ebicsEs": {
"signatureVersion": "A005",
"signatureB64": "string",
"ebicsUserId": "string",
"orderSha256B64": "string"
},
"reason": "string",
"channel": "string"
}
],
"status": "pending",
"createdAt": "string",
"updatedAt": "string"
},
"evaluation": {
"satisfied": false,
"missing": [
"string"
],
"approverCount": 0,
"nextSigner": {
"name": "string",
"step": 0,
"of": 0
},
"uncompletableReason": "string"
},
"lines": [
{
"paymentId": "string",
"executionDate": "string",
"endToEndId": "string",
"payee": "string",
"account": "string",
"amount": "string",
"currency": "string",
"remittance": [
"string"
]
}
],
"totals": [
{
"currency": "string",
"amount": "string",
"count": 0
}
],
"contentDigest": "string",
"bankKey": "string",
"environment": "string",
"erpDecision": {
"allowed": false,
"reason": "string"
}
}/approvals/policiesThe company's approval policies: how many approvers a payment needs, by amount, and who they are. Each row also says whether too few of its approvers are still active to reach the quorum (revisionRequired) and whether it governs every bank that has no policy of its own. Any company member or an API key.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Policies: the standard { items } envelope, one row per policy |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"name": "string",
"template": "single",
"enabled": false,
"requiredApprovals": 0,
"allowSelfApproval": false,
"perTransactionLimit": "string",
"perJournalLimit": "string",
"perTransactionMax": "string",
"requireOneFromEachGroup": false,
"orderedApproval": false,
"groups": [
{
"id": "string",
"name": "string",
"userIds": []
}
],
"id": "string",
"isDefault": false,
"status": "draft",
"createdBy": "string",
"locked": false,
"lockedAt": "string",
"lockedBy": "string",
"revisionRequired": false,
"activeApprovers": 0,
"requiredApprovers": 0,
"governsBanksWithoutPolicy": false,
"selfApprovalCappedByOrgSetting": false
}
]
}/approvals/bank-rostersOne row per covered bank (danske-dk). Never per connection: one connection may serve several country banks. bound: false means the bank inherits the company default (which is also what a bank added tomorrow gets); sharedWith names the other banks in the query that resolve to the same policy.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
bankKeys string | query | yes | Comma-separated covered bank keys (repeatable). |
| Status | Description |
|---|---|
| 200 | One roster row per covered bank |
| 400 | No bank keys named No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"bankKey": "string",
"policyId": "string",
"policyName": "string",
"builtIn": false,
"bound": false,
"approverUserIds": [
"string"
],
"requiredApprovals": 0,
"sharedWith": [
"string"
]
}
]
}/approvals/policyThe policy the send path enforces for the company, with the organisation's self-approval setting applied. selfApprovalCappedByOrgSetting inside policy is true when that setting caps what the stored policy allows.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | The active policy (`policy`) |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"policy": {
"name": "string",
"template": "single",
"enabled": false,
"requiredApprovals": 0,
"allowSelfApproval": false,
"perTransactionLimit": "string",
"perJournalLimit": "string",
"perTransactionMax": "string",
"requireOneFromEachGroup": false,
"orderedApproval": false,
"groups": [
{
"id": "string",
"name": "string",
"userIds": [
"string"
]
}
],
"id": "string",
"isDefault": false,
"status": "draft",
"createdBy": "string",
"locked": false,
"lockedAt": "string",
"lockedBy": "string",
"selfApprovalCappedByOrgSetting": false
}
}Accounts
Bank accounts registry + imported statements (session or API key)
/accountsEvery bank account the company holds, with its latest known balance. A linked account carries the link that joined its IBAN and local number. Keyset-paginated: limit and cursor, with an exact total.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
limit integer | query | no | |
cursor string | query | no |
| Status | Description |
|---|---|
| 200 | Accounts with latest balances |
| 400 | A `limit` that is not a number (`invalid_parameter`), or a malformed `cursor` (`invalid_cursor`). No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"id": "string",
"companyId": "string",
"iban": "string",
"accountOther": "string",
"country": "string",
"currency": "string",
"bankKey": "string",
"bankName": "string",
"name": "string",
"connected": false,
"lastReceivedData": "string",
"latestBalance": "string",
"latestBalanceCurrency": "string",
"latestBalanceDate": "string",
"createdAt": "string",
"updatedAt": "string",
"link": {
"linkedBy": "iban-tail",
"linkedAt": "string",
"reason": "string"
}
}
],
"nextCursor": "string",
"total": 0
}/accounts/statementsPer-account statement history, one page at a time, newest period first. A page is extended to the end of its last period, so it can hold more than limit statements; a period holding more than limit statements is split across pages. A non-null nextCursor means older statements exist. Pass it back as ?cursor=. On a production server a statement that arrived from a bank's test environment is not listed, nor counted in gaps.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
account string | query | yes | |
limit integer | query | no | Statements per page, counted before the page is extended to the end of its period. A page holds fewer than twice `limit`. |
cursor string | query | no | The previous answer's `nextCursor`. A malformed value is 400 `invalid_cursor`. |
| Status | Description |
|---|---|
| 200 | One page of statements + the detected sequence gaps for that page |
| 400 | `account_required`, `invalid_parameter` or `invalid_cursor` No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | Unknown account id for this company |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"documentId": "string",
"fromDate": "string",
"toDate": "string",
"sequenceNumber": "string",
"opening": {
"amount": "string",
"currency": "string",
"direction": "string",
"date": "string"
},
"closing": {
"amount": "string",
"currency": "string",
"direction": "string",
"date": "string"
},
"txCount": 0
}
],
"nextCursor": "string",
"account": "string",
"gaps": [
{
"kind": "balance",
"reason": "string",
"afterDocumentId": "string",
"beforeDocumentId": "string"
}
]
}Webhooks
Outbound event subscriptions (Admin session or API key)
/webhooksThe company's webhook endpoints, never their secrets. Each carries a 24-hour delivery health rollup and unknownEventTypes, the subscribed event names that can never match. The retry policy rides in meta.retryPolicy.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Subscriptions |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Admin required: a session without the Admin role is refused (`forbidden`). |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"id": "string",
"companyId": "string",
"url": "string",
"eventTypes": [
"string"
],
"description": "string",
"enabled": false,
"createdAt": "string",
"secretHint": "string",
"lastStatus": "delivered",
"lastAttemptAt": "string",
"lastError": "string",
"consecutiveFailures": 0,
"disabledAt": "string",
"disabledReason": "string",
"pendingSecretExpiresAt": "string",
"transport": "https",
"emailTo": "string",
"filterPredicate": {
"bankKey": "string",
"currency": "string",
"minAmount": 0,
"maxAmount": 0
},
"deliverySequence": 0,
"health": {
"windowHours": 0,
"delivered": 0,
"failed": 0,
"pending": 0,
"skippedDisabled": 0,
"skippedUnsubscribed": 0
},
"unknownEventTypes": [
"string"
]
}
],
"retryPolicy": {
"maxAttempts": 0,
"firstBackoffMs": 0,
"maxBackoffMs": 0,
"backoffJitter": 0,
"autoDisableAfterFailures": 0
}
}/webhooksRegisters 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, and so is a misspelt event NAME inside the array (e.g. payment.snet): delivery matches by exact string, so an unknown value would otherwise be stored and never fire. Omit eventTypes entirely (or send ["*"]) to receive every event in the published catalog; a type outside the catalog reaches only a subscription that names it.
X-API-Key header| Property | Type | Required | Description |
|---|---|---|---|
companyId | string | yes | |
url | string | yes | |
eventTypes | enum[] | no | Event names to subscribe to, e.g. ["payment.sent", "payment.rejected"], or ["*"] for every event in the published catalog. Omit for the same as "*". A type outside the catalog reaches only a subscription that names it. An explicitly-empty array is rejected; an unknown value 400s naming it. |
description | string | no | |
transport | enum | no | |
emailTo | string | no | |
filterPredicate | object | null | no |
{
"companyId": "string",
"url": "string",
"eventTypes": [
"*"
],
"description": "string",
"transport": "https",
"emailTo": "string",
"filterPredicate": {
"bankKey": "string",
"currency": "string",
"minAmount": 0,
"maxAmount": 0
}
}| Status | Description |
|---|---|
| 201 | Subscription created (returns the signing secret once) |
| 400 | The body is not JSON (`validation_failed`). Before `BankConnector-Version: 2026-10-20` a body that fails the route's schema is a 400 too. No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | `capability_disabled`: webhooks are not enabled for this organisation |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | The body is JSON but fails the route's schema, or names a field the route does not read (`validation_failed`, one entry per problem in `details.issues`). Pin `BankConnector-Version: 2026-10-20` to get 422; earlier pins answer 400. |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"endpoint": {
"id": "string",
"companyId": "string",
"url": "string",
"eventTypes": [
"string"
],
"description": "string",
"enabled": false,
"createdAt": "string",
"secretHint": "string",
"lastStatus": "delivered",
"lastAttemptAt": "string",
"lastError": "string",
"consecutiveFailures": 0,
"disabledAt": "string",
"disabledReason": "string",
"pendingSecretExpiresAt": "string",
"transport": "https",
"emailTo": "string",
"filterPredicate": {
"bankKey": "string",
"currency": "string",
"minAmount": 0,
"maxAmount": 0
},
"deliverySequence": 0,
"health": {
"windowHours": 0,
"delivered": 0,
"failed": 0,
"pending": 0,
"skippedDisabled": 0,
"skippedUnsubscribed": 0
},
"unknownEventTypes": [
"string"
]
},
"secret": "string"
}/webhooks/{id}Returns one webhook subscription's detail (includes deliverySequence).
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | The webhook subscription |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Admin required: a session without the Admin role is refused (`forbidden`). |
| 404 | Not found |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"endpoint": {
"id": "string",
"companyId": "string",
"url": "string",
"eventTypes": [
"string"
],
"description": "string",
"enabled": false,
"createdAt": "string",
"secretHint": "string",
"lastStatus": "delivered",
"lastAttemptAt": "string",
"lastError": "string",
"consecutiveFailures": 0,
"disabledAt": "string",
"disabledReason": "string",
"pendingSecretExpiresAt": "string",
"transport": "https",
"emailTo": "string",
"filterPredicate": {
"bankKey": "string",
"currency": "string",
"minAmount": 0,
"maxAmount": 0
},
"deliverySequence": 0,
"health": {
"windowHours": 0,
"delivered": 0,
"failed": 0,
"pending": 0,
"skippedDisabled": 0,
"skippedUnsubscribed": 0
},
"unknownEventTypes": [
"string"
]
}
}/webhooks/{id}Soft-deletes this webhook endpoint. The endpoint stops receiving events immediately and refuses enable/redeliver, but its delivery history stays readable via GET /webhooks/{id}/deliveries until each row's own 30-day retention expires.
?confirm=<id> is REQUIRED: without it the request is refused with 400 confirmation_required.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
confirm string | query | yes | The endpoint's own id. Always required for this route. |
companyId string | query | no | The organisation this request acts on. Read from here OR from `companyId` in the JSON body (this query parameter wins when both are sent). Required, in one of the two places, for an API key, a signed-in session or a workspace administrator. |
| Status | Description |
|---|---|
| 200 | Deleted |
| 400 | Confirmation required: re-send with ?confirm=<id> No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Admin required: a session without the Admin role is refused (`forbidden`). |
| 404 | Not found |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"removed": false
}/webhooks/{id}/enableTurns on an endpoint an Admin switched off with POST /webhooks/{id}/disable; it receives events again. An endpoint BankConnector switched off after repeated delivery failures is cleared with POST /webhooks/{id}/re-enable, which also resets its failure count.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Read from here OR from `companyId` in the JSON body (this query parameter wins when both are sent). Required, in one of the two places, for an API key, a signed-in session or a workspace administrator. |
| Property | Type | Required | Description |
|---|---|---|---|
companyId | string | no |
{
"companyId": "string"
}| Status | Description |
|---|---|
| 200 | Webhook enabled |
| 400 | The body is not JSON (`validation_failed`). Before `BankConnector-Version: 2026-10-20` a body that fails the route's schema is a 400 too. No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Admin required: a session without the Admin role is refused (`forbidden`). |
| 404 | Not found |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | The body is JSON but fails the route's schema, or names a field the route does not read (`validation_failed`, one entry per problem in `details.issues`). Pin `BankConnector-Version: 2026-10-20` to get 422; earlier pins answer 400. |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"endpoint": {
"id": "string",
"companyId": "string",
"url": "string",
"eventTypes": [
"string"
],
"description": "string",
"enabled": false,
"createdAt": "string",
"secretHint": "string",
"lastStatus": "delivered",
"lastAttemptAt": "string",
"lastError": "string",
"consecutiveFailures": 0,
"disabledAt": "string",
"disabledReason": "string",
"pendingSecretExpiresAt": "string",
"transport": "https",
"emailTo": "string",
"filterPredicate": {
"bankKey": "string",
"currency": "string",
"minAmount": 0,
"maxAmount": 0
},
"deliverySequence": 0,
"health": {
"windowHours": 0,
"delivered": 0,
"failed": 0,
"pending": 0,
"skippedDisabled": 0,
"skippedUnsubscribed": 0
},
"unknownEventTypes": [
"string"
]
}
}/webhooks/{id}/disableStops events reaching the endpoint without deleting it or its delivery history. Turn it back on with POST /webhooks/{id}/enable.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Read from here OR from `companyId` in the JSON body (this query parameter wins when both are sent). Required, in one of the two places, for an API key, a signed-in session or a workspace administrator. |
| Property | Type | Required | Description |
|---|---|---|---|
companyId | string | no |
{
"companyId": "string"
}| Status | Description |
|---|---|
| 200 | Webhook disabled |
| 400 | The body is not JSON (`validation_failed`). Before `BankConnector-Version: 2026-10-20` a body that fails the route's schema is a 400 too. No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Admin required: a session without the Admin role is refused (`forbidden`). |
| 404 | Not found |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | The body is JSON but fails the route's schema, or names a field the route does not read (`validation_failed`, one entry per problem in `details.issues`). Pin `BankConnector-Version: 2026-10-20` to get 422; earlier pins answer 400. |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"endpoint": {
"id": "string",
"companyId": "string",
"url": "string",
"eventTypes": [
"string"
],
"description": "string",
"enabled": false,
"createdAt": "string",
"secretHint": "string",
"lastStatus": "delivered",
"lastAttemptAt": "string",
"lastError": "string",
"consecutiveFailures": 0,
"disabledAt": "string",
"disabledReason": "string",
"pendingSecretExpiresAt": "string",
"transport": "https",
"emailTo": "string",
"filterPredicate": {
"bankKey": "string",
"currency": "string",
"minAmount": 0,
"maxAmount": 0
},
"deliverySequence": 0,
"health": {
"windowHours": 0,
"delivered": 0,
"failed": 0,
"pending": 0,
"skippedDisabled": 0,
"skippedUnsubscribed": 0
},
"unknownEventTypes": [
"string"
]
}
}/webhooks/{id}/testSends a signed ping to the endpoint and returns the delivery result.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Read from here OR from `companyId` in the JSON body (this query parameter wins when both are sent). Required, in one of the two places, for an API key, a signed-in session or a workspace administrator. |
| Property | Type | Required | Description |
|---|---|---|---|
companyId | string | no |
{
"companyId": "string"
}| Status | Description |
|---|---|
| 200 | Delivery result of the test event |
| 400 | The body is not JSON (`validation_failed`). Before `BankConnector-Version: 2026-10-20` a body that fails the route's schema is a 400 too. No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Admin required: a session without the Admin role is refused (`forbidden`). |
| 404 | Not found |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | The body is JSON but fails the route's schema, or names a field the route does not read (`validation_failed`, one entry per problem in `details.issues`). Pin `BankConnector-Version: 2026-10-20` to get 422; earlier pins answer 400. |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"ok": false,
"queued": false
}/webhooks/{id}/deliveriesPer-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.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
cursor string | query | no | The previous page's `nextCursor`, opaque. A malformed one is 400 `invalid_cursor`. |
limit integer | query | no |
| Status | Description |
|---|---|
| 200 | A page of delivery attempts |
| 400 | A `limit` that is not a number (`invalid_parameter`), or a malformed `cursor` (`invalid_cursor`). No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Admin required: a session without the Admin role is refused (`forbidden`). |
| 404 | Webhook not found |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"id": "whd_…",
"webhookId": "wh_…",
"companyId": "string",
"eventType": "payment.sent",
"payload": {},
"status": "delivered",
"httpStatus": 0,
"lastError": "string",
"attempts": 0,
"deliveredAt": "2026-01-01T00:00:00Z",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z",
"sequence": 0,
"expiresAt": "2026-01-01T00:00:00Z",
"sentAt": "2026-01-01T00:00:00Z"
}
],
"nextCursor": "string"
}/webhooks/{id}/deliveries/{deliveryId}/redeliverRe-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.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
deliveryId string | path | yes | |
companyId string | query | no | The organisation this request acts on. Read from here OR from `companyId` in the JSON body (this query parameter wins when both are sent). Required, in one of the two places, for an API key, a signed-in session or a workspace administrator. |
| Status | Description |
|---|---|
| 200 | Re-enqueued |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Admin required: a session without the Admin role is refused (`forbidden`). |
| 404 | Delivery not found / not redeliverable |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"redelivered": false,
"deliveryId": "string"
}/webhooks/{id}/redeliver-failedRe-enqueues failed deliveries for the webhook created at/after since. Bounded: a single call replays at most a fixed cap (500); while more remain, 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.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Read from here OR from `companyId` in the JSON body (this query parameter wins when both are sent). Required, in one of the two places, for an API key, a signed-in session or a workspace administrator. |
since string | query | yes | ISO timestamp: only failed deliveries created at/after this are replayed. |
cursor string | query | no | The previous call's `nextCursor`, opaque, to continue draining. A malformed one is 400 `invalid_cursor`. |
| Status | Description |
|---|---|
| 200 | Re-enqueued count + drain cursor |
| 400 | A missing or invalid `since` (`invalid_since`), or a malformed `cursor` (`invalid_cursor`). No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Admin required: a session without the Admin role is refused (`forbidden`). |
| 404 | Webhook not found |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"count": 0,
"nextCursor": "string"
}/webhooks/{id}/re-enableFor an endpoint BankConnector switched off after repeated delivery failures (POST /webhooks/{id}/enable is for one an Admin disabled). Clears the auto-disabled state: resets consecutiveFailures to 0 and clears disabledAt/disabledReason. 400 if the webhook is not currently disabled.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Read from here OR from `companyId` in the JSON body (this query parameter wins when both are sent). Required, in one of the two places, for an API key, a signed-in session or a workspace administrator. |
| Status | Description |
|---|---|
| 200 | Re-enabled |
| 400 | Webhook is not disabled No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Admin required: a session without the Admin role is refused (`forbidden`). |
| 404 | Webhook not found |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"webhookId": "string",
"enabledAt": "string"
}/webhooks/{id}/rotate-secretGenerates 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.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Read from here OR from `companyId` in the JSON body (this query parameter wins when both are sent). Required, in one of the two places, for an API key, a signed-in session or a workspace administrator. |
| Property | Type | Required | Description |
|---|---|---|---|
companyId | string | no | |
overlapHours | number | no | How long the old secret stays valid. |
{
"companyId": "string",
"overlapHours": 0
}| Status | Description |
|---|---|
| 200 | Rotated: newSecret shown once |
| 400 | Invalid body No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Admin required: a session without the Admin role is refused (`forbidden`). |
| 404 | Webhook not found |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | The body is JSON but fails the route's schema, or names a field the route does not read (`validation_failed`, one entry per problem in `details.issues`). Pin `BankConnector-Version: 2026-10-20` to get 422; earlier pins answer 400. |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
{
"webhookId": "string",
"newSecret": "string",
"overlapUntil": "string"
}User Management
Create, invite, and manage users within a company.
/usersReturns all users in the company (name, email, roles, status).
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Company users |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"id": "string",
"email": "string",
"name": "string",
"roles": [
"admin"
],
"status": "active",
"totpEnabledAt": "string",
"emailDeliveryFlag": {
"kind": "hard_bounce",
"at": "string"
},
"erpLinked": false,
"namedInPolicy": false,
"approverBanks": [
{
"bankKey": "string",
"displayName": "string"
}
]
}
]
}/usersCompany admin only. Sends an invite email: the user receives a link to /auth/set-password where they set their own password. Nobody else ever chooses it, so the request takes no 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.
Step-up: creating a user with the admin or approver role needs the acting admin's current authenticator code (or a backupCode) when they are enrolled in 2FA or their company requires it, and a passed step-up rotates a cookie session. A viewer-only user needs no code. An API key can invite viewer users only: it has no second factor to give, so an admin or approver is refused (403).
An address that already has a login elsewhere (another organisation, a partner firm) gets the same 201 and body. No login is made, because a login belongs to one organisation, but the person is listed in GET /users as invited and every /users/{id} route answers for them as for any invited user. The address's holder is emailed that it already has a login, instead of a set-password link. An address already invited to or in this organisation is a 409.
X-API-Key header| Property | Type | Required | Description |
|---|---|---|---|
companyId | string | yes | |
email | string | yes | |
name | string | yes | |
roles | string[] | yes | |
code | string | no | The acting admin's 2FA TOTP code (when step-up applies; an `admin` or `approver` role). |
backupCode | string | no | A 2FA backup code, in place of `code`. |
{
"companyId": "string",
"email": "approver@acme.com",
"name": "Bob Approver",
"roles": [
"approver"
],
"code": "string",
"backupCode": "string"
}| Status | Description |
|---|---|
| 201 | User invited |
| 400 | Invalid request (`bad_request`), or no valid roles (`invalid_roles`) No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Company admin required (`forbidden`); an API key creating an `admin` or `approver`. |
| 409 | The email address already belongs to a user of this organisation (`conflict`) |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | Step-up refused: `totp_required` (the 2FA code is missing or wrong) or `totp_enrollment_required` (the company requires 2FA and this admin has not set it up) |
| 429 | Too many wrong 2FA codes (`rate_limited`); see `Retry-After` |
| 503 | An invite with no email transport to deliver it (`invites_unavailable`), refused before the code is checked so a single-use code is not spent |
{
"id": "string",
"companyId": "string",
"email": "string",
"name": "string",
"roles": [
"admin"
],
"status": "active",
"settings": {
"locale": "string",
"timezone": "string",
"dateFormat": "iso",
"notifications": {
"paymentNeedsApproval": false,
"paymentApproved": false,
"paymentRejected": false,
"statementReceived": false,
"erpDecisionReceipt": false
}
},
"totpEnabledAt": "string",
"emailDeliveryFlag": {
"kind": "hard_bounce",
"at": "string"
},
"createdAt": "string",
"invited": false,
"emailDeliveryPending": false,
"inviteLink": "string"
}/users/{id}Returns one user's record. Readable by any authenticated company member, but the response shape depends on who is asking:
- An API key, the subject themselves, or a session holding the
adminrole receive the whole record:settings,companyIdandcreatedAtincluded. - Any other signed-in company member receives the narrower roster projection (
id,email,name,roles,status,totpEnabledAt). The same six fieldsGET /userspublishes. A colleague's preferences are not theirs to read.
Both are a 200; the status code does not distinguish them, so read the 200 schema's two arms.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | The user: the whole record for an API key / the subject / an admin session, the roster projection for any other company member. See the operation description; the schema's two arms are that condition, not a caller's free choice. |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 404 | Unknown user in this company |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"id": "string",
"companyId": "string",
"email": "string",
"name": "string",
"roles": [
"admin"
],
"status": "active",
"settings": {
"locale": "string",
"timezone": "string",
"dateFormat": "iso",
"notifications": {
"paymentNeedsApproval": false,
"paymentApproved": false,
"paymentRejected": false,
"statementReceived": false,
"erpDecisionReceipt": false
}
},
"totpEnabledAt": "string",
"totpLastStep": 0,
"emailDeliveryFlag": {
"kind": "hard_bounce",
"at": "string"
},
"createdAt": "string"
}Workspace
Company dashboard, client error and absence reports, bank coverage requests, and per-bank test payments
/dashboard/kpisCounts and per-currency payment totals for the Home screen's KPI row and payment-pipeline card, aggregated in SQL over every outbound payment the company holds, not a page of them. The window is the calendar day named by day, resolved in the IANA zone named by timezone (the signed-in user's preference zone), so the day boundary matches every timestamp rendered beside these figures. Payment totals are returned as exact decimal strings keyed by ISO 4217 code.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
day string | query | yes | The calendar day to window on, as YYYY-MM-DD. A value carrying a time is rejected. |
timezone string | query | no | IANA zone the day is resolved in, e.g. Europe/Copenhagen. Defaults to UTC. |
| Status | Description |
|---|---|
| 200 | KPI aggregate for the requested day |
| 400 | Missing or malformed `day`, or an unknown `timezone` No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"day": "string",
"timezone": "string",
"yesterdayCount": 0,
"pendingCount": 0,
"execCount": 0,
"execCountByCurrency": {},
"rejectedOpenCount": 0,
"todayByStatus": [
{
"status": "string",
"currency": "string",
"count": 0,
"bankKeys": [
"string"
]
}
],
"execMoney": {},
"awaitMoney": {}
}/bank-coverage-requestsAny signed-in user (or anonymous) can submit a BIC or bank name for a bank BankConnector does not yet cover. Not a request sent to the bank itself. It is queued for the development team.
X-API-Key header| Property | Type | Required | Description |
|---|---|---|---|
query | string | yes | BIC code or bank name. |
email | object | no |
{
"query": "string",
"email": "string"
}| Status | Description |
|---|---|
| 201 | Request recorded |
| 400 | Empty query |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | The body is JSON but fails the route's schema, or names a field the route does not read (`validation_failed`, one entry per problem in `details.issues`). Pin `BankConnector-Version: 2026-10-20` to get 422; earlier pins answer 400. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"ok": false,
"request": {
"id": "string",
"query": "string",
"createdAt": "string"
}
}/banks/{bankKey}/test-scenariosLists 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.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
bankKey string | path | yes | |
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Offered scenarios |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"scenarios": [
{
"id": "domestic",
"label": "string",
"description": "string",
"amount": "string",
"currency": "string",
"creditorName": "string",
"paymentType": "string"
}
]
}System
Health, readiness and FX reference rates
/healthAlways 200 while the process is up. status is strictly serviceability. A serving box says "ok" even when non-fatal startup config warnings exist; those ride in the advisory warnings field as stable machine codes. Use /ready for the orchestrator serviceability probe.
| Status | Description |
|---|---|
| 200 | Server is running |
{
"status": "string",
"warnings": [
"string"
]
}/readyReadiness probe for orchestrators (distinct from /health liveness): 200 when the app can serve. The database is reachable and its schema up to date when one is configured, else 503 so the load balancer keeps traffic away. Public, like /health.
| Status | Description |
|---|---|
| 200 | Ready |
| 503 | Not ready: the web app is not loaded, the database schema is not up to date, or the database is unreachable. `error.details.reason` is one of `frontend_bundle_missing`, `schema_not_migrated`, `database_unreachable` so a caller can branch on which; `error.details.ready` is always `false`. |
{
"ready": false
}/fx/ratesGlobal (non-tenant) indicative EUR-base FX rates for converting balances to a common currency. Read-only, kept fresh (refreshed once a day). Shape: { base, rates, asOf }.
X-API-Key header| Status | Description |
|---|---|
| 200 | Cached EUR-base rates |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"base": "string",
"rates": {},
"asOf": "string"
}Tenancy
/company/approval-settingsReadable by any authenticated member of the organisation, forbidSelfApproval decides whether the person who created a payment may approve it, so everyone subject to it can see it. Scope comes from the caller's own context (or an explicit companyId query param for a per-request caller).
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Approval settings |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"approvalSettings": {
"forbidSelfApproval": false,
"updatedAt": "string",
"updatedBy": "string"
}
}/company/payee-verificationReadable by any authenticated member. Whether a SEPA file of several payments sent over EBICS to a bank in Germany, or to Oberbank, Raiffeisen Bank International, UniCredit Bank Austria or Erste Group Bank in Austria, goes out with the bank's payee check (Verification of Payee, opt-in) or without it (opt-out); at every other Austrian bank each file keeps the opt-out order type whatever this setting says. A file of one payment at a covered bank is always checked. opt-in when never set.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | The payee-verification setting |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"payeeVerification": {
"batches": "opt-in",
"updatedAt": "string",
"updatedBy": "string"
}
}/partner-access/requestsEvery request made against the caller's own organisation, most recent first. Readable by any authenticated member: the same 'everyone governed by it may see it' posture /company/approval-settings uses.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | Incoming requests |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"id": "string",
"orgId": "string",
"firmId": "string",
"firmName": "string",
"requestedBy": "string",
"requestedByName": "string",
"requestedLevel": "status",
"status": "pending",
"createdAt": "string",
"decidedAt": "string",
"decidedBy": "string"
}
]
}/partner-access/firmsEach partner firm holding a live grant on the caller's organisation: its level, when and by whom it was granted, and every ACTIVE person of the firm, each of whom can enter this organisation now. Readable by any authenticated member.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | The firms with access |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"firmId": "string",
"firmName": "string",
"level": "status",
"grantedAt": "string",
"grantedBy": "string",
"consultants": [
{
"userId": "string",
"name": "string",
"email": "string",
"role": "partner-admin"
}
]
}
]
}/partner-access/firm-choicesEvery active partner firm on BankConnector, whether or not the general signup lists it: an id and a display name each, and nothing else. The id is what POST /partner-access/firms/{firmId}/grant takes. Readable by any authenticated member; a partner firm's consultant sees its own firm only.
X-API-Key header| Status | Description |
|---|---|
| 200 | The partner firms |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"id": "string",
"displayName": "string"
}
]
}/partner-access/deleted-firmsEach partner firm BankConnector has deleted that introduced the caller's organisation, held access to it or asked for access: its name, when it was deleted, and whether it is credited with the introduction. A deleted firm's access and open requests ended with it, and its people were erased. Readable by any authenticated member; a partner firm's consultant sees none.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
companyId string | query | no | The organisation this request acts on. Required with an API key. A signed-in company user falls back to its own company when this is omitted, and a workspace administrator to the company selected with `POST /auth/company-context`. |
| Status | Description |
|---|---|
| 200 | The deleted partner firms |
| 400 | No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Forbidden: the credential's role, scope or grant does not allow this operation. |
| 429 | Too many requests from this network (`rate_limited`); wait `Retry-After` seconds. |
{
"items": [
{
"firmId": "string",
"firmName": "string",
"deletedAt": "string",
"introduced": false
}
]
}/users/{id}/rolesPUT = full replacement of the role array (there is no partial role patch). Reports approval-quorum impact in both directions: approving is role-gated, so removing the Approver role from someone an approval policy names can block a bank's sends, and granting it can release one. Step-up: a change that GRANTS Admin or Approver (a role the user does not hold now) needs the acting admin's current authenticator code (or a backupCode) when they are enrolled in 2FA or their company requires it, and a passed step-up rotates a cookie session. Removing a role, granting Viewer, or re-sending roles the user already holds needs no code. An API key cannot grant Admin or Approver at all (403), nor change the roles of a user who holds Admin.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes |
| Property | Type | Required | Description |
|---|---|---|---|
companyId | string | yes | |
roles | string[] | yes | |
code | string | no | 2FA TOTP code (when step-up applies). |
backupCode | string | no | A 2FA backup code, in place of `code`. |
{
"companyId": "string",
"roles": [
"string"
],
"code": "string",
"backupCode": "string"
}| Status | Description |
|---|---|
| 200 | Updated user |
| 400 | The body is not JSON (`validation_failed`). Before `BankConnector-Version: 2026-10-20` a body that fails the route's schema is a 400 too. No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | The caller may not make this change: an API key granting Admin or Approver, or changing an Admin's roles |
| 404 | No such user in this company (`not_found`), answered before any code is checked so a single-use code is not spent |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | Step-up refused: `totp_required` (the 2FA code is missing or wrong) or `totp_enrollment_required` (the company requires 2FA and this admin has not set it up) |
| 429 | Too many wrong 2FA codes (`rate_limited`); see `Retry-After` |
{
"user": {
"id": "string",
"companyId": "string",
"email": "string",
"name": "string",
"roles": [
"admin"
],
"status": "active",
"settings": {
"locale": "string",
"timezone": "string",
"dateFormat": "iso",
"notifications": {
"paymentNeedsApproval": false,
"paymentApproved": false,
"paymentRejected": false,
"statementReceived": false,
"erpDecisionReceipt": false
}
},
"totpEnabledAt": "string",
"emailDeliveryFlag": {
"kind": "hard_bounce",
"at": "string"
},
"createdAt": "string"
},
"sessionsRevoked": 0,
"banksPaused": [
"string"
],
"banksResumed": [
"string"
]
}/users/{id}/reinviteRe-sends the invite to a user who has not accepted it yet: a fresh one-time link replaces the old one, which stops working. An Admin or an API key. A user who already set a password has no invite to re-send (404); to let them back in, an Admin resets their password.
X-API-Key header| Name | In | Required | Description |
|---|---|---|---|
id string | path | yes |
| Property | Type | Required | Description |
|---|---|---|---|
companyId | string | no |
{
"companyId": "string"
}| Status | Description |
|---|---|
| 200 | Invite re-sent |
| 400 | The body is not JSON (`validation_failed`). Before `BankConnector-Version: 2026-10-20` a body that fails the route's schema is a 400 too. No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`. |
| 401 | Authentication required: the credential is missing, invalid or expired. |
| 403 | Admin required: a session without the Admin role is refused (`forbidden`). |
| 404 | No pending invite for this user in this company (`not_found`) |
| 413 | The request body is larger than this operation accepts (`payload_too_large`). |
| 422 | The body is JSON but fails the route's schema, or names a field the route does not read (`validation_failed`, one entry per problem in `details.issues`). Pin `BankConnector-Version: 2026-10-20` to get 422; earlier pins answer 400. |
| 429 | Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds. |
| 503 | No email transport to deliver the invite (`invites_unavailable`) |
{
"id": "string",
"companyId": "string",
"email": "string",
"name": "string",
"roles": [
"admin"
],
"status": "active",
"settings": {
"locale": "string",
"timezone": "string",
"dateFormat": "iso",
"notifications": {
"paymentNeedsApproval": false,
"paymentApproved": false,
"paymentRejected": false,
"statementReceived": false,
"erpDecisionReceipt": false
}
},
"totpEnabledAt": "string",
"emailDeliveryFlag": {
"kind": "hard_bounce",
"at": "string"
},
"createdAt": "string",
"invited": false,
"emailDeliveryPending": false,
"inviteLink": "string"
}