sandbox.bankconnector.com API Reference

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

GET/profiles
List all banks with the payment-type codes they offer

Returns 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.

Credentials API key, sent as the X-API-Key header
Responses
StatusDescription
200List of bank profiles
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "items": [
    {
      "key": "string",
      "bankName": "string",
      "formerNames": [
        "string"
      ],
      "acceptedDebtorBics": [
        "string"
      ],
      "countryCode": "string",
      "painVersion": "string",
      "evidenceTier": "live",
      "unsupported": {
        "reason": "string",
        "contactSupport": false
      },
      "brand": "string",
      "paymentTypes": [
        "string"
      ]
    }
  ]
}
Code samples
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()
GET/profiles/members
List the Sparkassen and cooperative banks the German group profiles serve

Germany'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.

Credentials API key, sent as the X-API-Key header
Responses
StatusDescription
200Every member institution
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "items": [
    {
      "name": "string",
      "town": "string",
      "bic": "string",
      "bankCode": "string",
      "otherBankCodes": [
        "string"
      ],
      "formerBankCodes": [
        "string"
      ],
      "countryCode": "string",
      "bankKey": "string",
      "ebics": {
        "url": "string",
        "hostId": "string"
      }
    }
  ]
}
GET/payment-types
Full payment-type catalog

All payment types in the shared catalog with ISO 20022 encoding, descriptions, and requirements.

Credentials API key, sent as the X-API-Key header
Responses
StatusDescription
200Full catalog
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "items": [
    {
      "code": "string",
      "label": "string",
      "description": "string",
      "requires": [
        "string"
      ],
      "channel": "string",
      "isoEncoding": {
        "serviceLevel": "string",
        "localInstrument": "string",
        "categoryPurpose": "string"
      }
    }
  ]
}
GET/banks/{bankKey}/payment-types
Payment types offered by a specific bank

The 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
bankKey BankKeypathyes
Responses
StatusDescription
200Bank key/name + offered payment types with full metadata
401Authentication required: the credential is missing, invalid or expired.
403On the sandbox: the organisation has no connection to this bank yet, so its payment types are not listed
404Unknown bank
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
    ]
  }
}
GET/banking-days/adjust
The banking day on or next to a date

Returns 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
bankKey stringqueryyesThe debtor's bank, as `GET /profiles` lists it (e.g. `danske-dk`). Its country's bank holidays close the day.
currency stringqueryyesISO 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 stringqueryyesThe payment date to check, `YYYY-MM-DD`, between 2000-01-01 and 2099-12-31.
direction enumqueryyesWhich 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 stringquerynoISO 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.
Responses
StatusDescription
200The banking day
400A 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`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "date": "string",
  "moved": false,
  "reason": "weekend",
  "holidayName": "string"
}
GET/banks/live
The organisation's banks with a live connection

One 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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`.
Responses
StatusDescription
200The live banks, ordered by bank key and then environment
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "items": [
    {
      "bankKey": "string",
      "bankName": "string",
      "environment": "test",
      "live": false
    }
  ]
}
GET/banks/{bankKey}/status
Whether a bank is supported, and how its payments are delivered

For 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
bankKey BankKeypathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Bank support status
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404Unknown bank
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "bankKey": "string",
  "deliveryMode": "converted-only",
  "hostToHost": false
}

Conversion

Validate and convert canonical payment JSON

POST/validate/{bankKey}
Validate a payment instruction (no conversion)

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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
bankKey BankKeypathyes
companyId stringquerynoThe company whose timezone the execution-date rules judge in. Optional: without it, and without a selected company, those rules use UTC.
Request body required
PropertyTypeRequiredDescription
schemaVersionenumnoCanonical contract version; defaults to the current version when omitted.
messageIdstringyes
creationDateTimestringno
initiatingPartyobjectyes
paymentsPayment[]yes
Example
{
  "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": {}
        }
      ]
    }
  ]
}
Responses
StatusDescription
200Validation outcome (valid or not: check `valid`)
400Malformed JSON body No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404Unknown bank key
413The request body is larger than this operation accepts (`payload_too_large`).
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "valid": false,
  "issues": [
    {
      "code": "string",
      "level": "generic",
      "severity": "error",
      "message": "string",
      "path": "string",
      "bank": "string"
    }
  ]
}
Code samples
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()
POST/convert/{bankKey}
Validate then convert to bank-specific pain.001 XML

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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
bankKey BankKeypathyes
validate booleanquerynoIf true, XSD-validate the generated XML before returning it.
painVersion stringquerynoThe 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 stringquerynoThe company whose timezone the execution-date rules judge in. Optional: without it, and without a selected company, those rules use UTC.
Request body required
PropertyTypeRequiredDescription
schemaVersionenumnoCanonical contract version; defaults to the current version when omitted.
messageIdstringyes
creationDateTimestringno
initiatingPartyobjectyes
paymentsPayment[]yes
Example
{
  "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": {}
        }
      ]
    }
  ]
}
Responses
StatusDescription
200Bank-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.
400Malformed JSON body No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404Unknown bank key
413The request body is larger than this operation accepts (`payload_too_large`).
422Validation failed: returns all issues at once
429Too 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
Code samples
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 XML
import 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
POST/convert-async/{bankKey}
Enqueue a background conversion (returns a job id)

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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
bankKey BankKeypathyes
validate booleanquerynoNot 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 stringquerynoThe 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 stringquerynoThe company whose timezone the execution-date rules judge in. Optional: without it, and without a selected company, those rules use UTC.
Request body required
PropertyTypeRequiredDescription
schemaVersionenumnoCanonical contract version; defaults to the current version when omitted.
messageIdstringyes
creationDateTimestringno
initiatingPartyobjectyes
paymentsPayment[]yes
Example
{
  "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": {}
        }
      ]
    }
  ]
}
Responses
StatusDescription
202Conversion 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.
400Malformed 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`.
401Authentication required: the credential is missing, invalid or expired.
403The 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.
404Unknown bank key
413The request body is larger than this operation accepts (`payload_too_large`).
422Validation failed: returns all issues at once
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (202)
{
  "jobId": "string",
  "status": "pending",
  "bankKey": "string",
  "autoPaymentType": "string",
  "unknownFieldsDropped": [
    "string"
  ],
  "warnings": [
    {
      "code": "string",
      "level": "generic",
      "severity": "error",
      "message": "string",
      "path": "string",
      "bank": "string"
    }
  ]
}
GET/convert-jobs/{id}
Poll a background conversion job

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).

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
Responses
StatusDescription
200Terminal 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.
202Job still pending or running, poll again. Same job-status body as the 200.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404Conversion job not found (or not owned by the caller)
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "jobId": "string",
  "status": "pending",
  "createdAt": "string",
  "updatedAt": "string",
  "failure": {
    "code": "string",
    "message": "string",
    "details": {
      "lastError": "string"
    }
  }
}
POST/payment-types/suggest
Suggest the payment type for a payment

Infers 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.

Credentials API key, sent as the X-API-Key header
Request body required
PropertyTypeRequiredDescription
bankKeystringyes
paymentobjectyes
Example
{
  "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": {}
        }
      }
    ]
  }
}
Responses
StatusDescription
200The suggestion
400`validation_failed`: the body is not JSON, or `bankKey` or `payment` is missing
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404`bank_not_found`: no bank with that key
413The request body is larger than this operation accepts (`payload_too_large`).
422`validation_failed`: `payment` is structurally wrong
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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)

GET/connectivity/profiles
List connectivity profiles (channel info per bank)

Read-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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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 enumqueryno`badge` projects each row down to identity + channel badge. Omit it for the full connection declaration. Any other value is a 400 `invalid_parameter`.
Responses
StatusDescription
200Connectivity profiles
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
    }
  ]
}
GET/connections
List bank connections for a company

The 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Connection list
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
        ]
      }
    }
  ]
}
GET/connections/{id}
Get a single connection and its readiness

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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Connection + readiness
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404Not found
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
  }
}
GET/connections/{id}/health
Channel health for one connection

The 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Channel health
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404Not found
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
  }
}
POST/connections/{id}/intraday-report
Fetch the intraday account report now

Asks 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200The reports this request ingested, or, inside the 15 minutes after the last fetch, that fetch's reports again
400The 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`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404No such connection for this company (`connection_not_found`)
409The connection is being polled right now; try again in a minute
422No intraday report to fetch on this connection: it is not active, its bank offers none, or it is a test connection
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
502The bank could not be reached
Example response (200)
{
  "connectionId": "string",
  "ingested": [
    {
      "documentId": "string",
      "type": "string",
      "name": "string"
    }
  ],
  "skipped": 0,
  "complete": false,
  "fetchedAt": "string",
  "reused": false,
  "nextFetchAt": "string"
}
GET/connections/{id}/test-files
The SFTP test round's files, grouped by country

The 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200The test files, grouped by country
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404Not found
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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
}
GET/connections/{id}/intro-email
Render the bank intro email (draft to send to the bank contact)

The 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Rendered email (subject + body)
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404Not found
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "email": {
    "to": "string",
    "subject": "string",
    "body": "string"
  }
}
GET/connections/{id}/capability-sheet
Download the host-to-host capability sheet (PDF, base64-encoded)

The 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200PDF as base64, plus the fields the sheet could not state
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404Connection not found
409The bank agreement pins a pain.001 version this bank does not support
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "pdfBase64": "string",
  "filename": "string",
  "unknowns": [
    "string"
  ],
  "pages": 0
}
GET/banks/{bankKey}/setup-info
Per-bank setup descriptor for the wizard UI

Returns 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
bankKey BankKeypathyes
companyId stringquerynoThe 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 enumquerynoWhether 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 enumquerynoThe 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.
Responses
StatusDescription
200Setup descriptor
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404Unknown bank
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
  }
}
GET/banks/{bankKey}/sftp-naming
Preview the SFTP logins, folders and file names a gateway bank's agreement gives

For 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
bankKey BankKeypathyes
companyId stringquerynoThe 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 stringquerynoSFTP account code
orgNo stringqueryno9-digit organisation number
custId stringquerynoCustomer identifier (CUSTID)
Responses
StatusDescription
200The test and production logins, folders and file names
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication 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.
404Unknown bank, or a bank whose server, logins and file names are not derived
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
  }
}
GET/banks/{bankKey}/settings
Get per-company settings for a specific bank

The 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
bankKey BankKeypathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Bank settings (null if none saved)
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
  }
}
GET/bank-settings
List all per-company bank settings in one call

Every 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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`.
Responses
StatusDescription
200All saved bank settings for this company
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
      }
    }
  ]
}
GET/connections/{id}/ebics-letter
Download the EBICS initialisation letter (PDF, base64-encoded)

Returns 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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 stringquerynoAn 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 enumqueryno`ini` or `hia` returns that one letter; omit it for both. Any other value is ignored.
Responses
StatusDescription
200PDF as base64
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404No 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`).
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "pdfBase64": "string",
  "filename": "string"
}
GET/connections/{id}/ebics-subscribers
List the approvers enrolled on an EBICS connection (per-user mode)

Each 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200The enrolled subscribers
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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)

POST/journal/payments
Submit a payment: convert, journal and, on host-to-host, deliver

The 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).

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
Idempotency-Key stringheadernoUnique 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.
Request body required
PropertyTypeRequiredDescription
companyIdstringyesThe company this payment belongs to.
bankKeyBankKeyyes
paymentPaymentInstructionyes
environmentenumnoWhich connection delivers the payment. Defaults to production; pass "test" to dispatch over the bank's test channel.
validatebooleannoAlso XSD-validate the generated XML.
idempotencyKeystringnoAlternative to the Idempotency-Key header.
makerobjectnoWho 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.
sourceobjectnoThe 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.
erpNotesobject[]noFacts 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.
Example
{
  "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"
    }
  ]
}
Responses
StatusDescription
201Payment 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`.
401Authentication required: the credential is missing, invalid or expired.
402Two 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.
403Scope 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.
413The request body is larger than this operation accepts (`payload_too_large`).
422Validation failed: all issues at once
429Rate limited (production): see Retry-After
Example response (201)
{
  "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"
  }
}
Code samples
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()
POST/journal/payments/preview
Preview a payment: convert, validate and return the XML (not journaled, not sent)

Read-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.

Credentials API key, sent as the X-API-Key header
Request body required
PropertyTypeRequiredDescription
companyIdstringyesThe company this payment belongs to.
bankKeyBankKeyyes
paymentPaymentInstructionyes
environmentenumnoWhich connection delivers the payment. Defaults to production; pass "test" to dispatch over the bank's test channel.
validatebooleannoAlso XSD-validate the generated XML.
idempotencyKeystringnoAlternative to the Idempotency-Key header.
makerobjectnoWho 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.
sourceobjectnoThe 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.
erpNotesobject[]noFacts 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.
Example
{
  "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"
    }
  ]
}
Responses
StatusDescription
200Preview generated. Carries the same `warnings` a real submit would return (present only when the payment raised non-blocking warnings).
400Missing fields No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
413The request body is larger than this operation accepts (`payload_too_large`).
422Validation / XSD failed
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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
  }
}
POST/journal/payee-verifications
Verify payees: send a verification-only file to the bank (Verification of Payee)

Opt-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.

Credentials API key, sent as the X-API-Key header
Request body required
PropertyTypeRequiredDescription
companyIdstringyesYour company id.
bankKeystringyesThe bank, e.g. `op-financial-group-fi`.
payment-yesThe canonical payment instruction whose payees are checked.
environmentenumnoWhich connection sends the file. Defaults to `production`.
Example
{
  "companyId": "string",
  "bankKey": "string",
  "environment": "test"
}
Responses
StatusDescription
202The 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`.
400Missing 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`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404No active connection to the bank in that environment (`connection_not_found`)
413The request body is larger than this operation accepts (`payload_too_large`).
422The 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
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
502The bank could not be reached, or its answer could not be verified (`bank_unavailable`); sending again is safe
Example response (202)
{
  "messageId": "string",
  "requestMessageId": "string",
  "bankKey": "string",
  "environment": "test",
  "transactionCount": 0,
  "endToEndIds": [
    "string"
  ],
  "reference": "string",
  "painVersion": "string",
  "xml": "string",
  "sentAt": "2026-01-01T00:00:00Z"
}
GET/journal/payments/batch
Look up a submitted batch by its messageId: each payment's state, or no record of it

For 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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 stringqueryyesThe 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.
Responses
StatusDescription
200What 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`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
    }
  }
}
POST/journal/ingest
Ingest a bank file (camt.053/054, pain.002, MT940, CFONB 120) into normalised JSON

Upload a raw bank statement/status file; it is parsed to the one normalised shape and journaled. Requires authentication.

Credentials API key, sent as the X-API-Key header
Request body required
PropertyTypeRequiredDescription
companyIdstringyesYour company id.
bankKeystringno
contentstringyesRaw file contents
environmentenumno
Example
{
  "companyId": "string",
  "bankKey": "string",
  "content": "string",
  "environment": "test"
}
Responses
StatusDescription
200Dedupe hit: this content was already ingested. Returns the existing document unchanged; no events or status advances re-run.
201File ingested and normalised: every statement block of the file was ingested. A file whose blocks did not all end the same way answers 207.
207Partial: 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.
400The 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`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
413The request body is larger than this operation accepts (`payload_too_large`).
422Unrecognised / 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.
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
    }
  }
}
GET/journal/documents
List journal document summaries (newest first)

An 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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 enumqueryno
type enumqueryno
collected booleanquerynoFilter 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 stringquerynoFree-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 stringquerynoOne 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 stringquerynoLower 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 stringquerynoUpper bound on any of the document's totals, inclusive. Same semantics as `amountMin`.
dateFrom stringquerynoOnly documents created on or after this day (`YYYY-MM-DD`, inclusive, server timezone).
dateTo stringquerynoOnly documents created on or before this day (`YYYY-MM-DD`, **inclusive**; the whole day is covered).
accountId stringquerynoOnly 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 booleanqueryno`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 integerquerynoPage 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 stringquerynoOpaque cursor from a prior page's `nextCursor`. A malformed cursor is a 400 `invalid_cursor`.
Responses
StatusDescription
200Document summaries
400A `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`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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
}
POST/journal/export
Export full document payloads by journal number (admin / API key)

Bulk 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.

Credentials API key, sent as the X-API-Key header
Request body required
PropertyTypeRequiredDescription
companyIdstringyesYour company id.
journalNosstring[]yes
filter-no
Example
{
  "companyId": "string",
  "journalNos": [
    "string"
  ]
}
Responses
StatusDescription
200Exported documents + any notFound journal numbers
207Partial 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`.
401Authentication required: the credential is missing, invalid or expired.
403Admin required (human session)
413The request body is larger than this operation accepts (`payload_too_large`).
422The 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.
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "exported": [
    {
      "journalNo": "string",
      "documentId": "string",
      "direction": "inbound",
      "type": "pain.001",
      "sourceFormat": "string",
      "bankKey": "string",
      "createdAt": "string",
      "wasAlreadyCollected": false,
      "transactionCount": 0
    }
  ],
  "notFound": [
    "string"
  ]
}
GET/journal/list
Paginated journal feed (cursor): the ERP read + collect surface

Tenant-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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoYour company id.
cursor stringquerynoOpaque cursor from a prior page's `nextCursor`.
limit integerquerynoPage size: default 500, max 1000 (larger than the interactive /journal/documents feed, for bulk ERP pulls).
direction enumqueryno
type enumqueryno
uncollectedOnly booleanquerynoWhen `true`, returns only documents not yet collected (the standard poll-then-collect loop).
Responses
StatusDescription
200A page of documents + counts + nextCursor
400A `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`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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
    }
  ]
}
GET/journal/documents/{id}
Fetch one journal document with its full payload

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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Document + payload
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404Not found (or not in this tenant's scope)
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
      }
    }
  ]
}
GET/journal/documents/{id}/payments
Per-payment lines within an outbound batch

The 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Per-line payments (payee, amount, endToEndId, status)
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: 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)
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
  }
}
GET/journal/documents/{id}/file
Download the stored bank file for a document

Returns 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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 enumqueryno`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.
Responses
StatusDescription
200The stored XML (attachment). The generated pain.001 outbound, the bank's original camt inbound
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404No 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)
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
GET/journal/documents/{id}/export.csv
Download a bank statement as the ERP-import CSV

Returns 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200The statement as CSV (attachment)
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404No 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
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
GET/journal/documents/{id}/export.camt053
Download a bank statement as camt.053.001.02 for ERP import

Returns 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200The statement as camt.053.001.02 (attachment)
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404No bank statement to export for this document
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
GET/journal/payment-types
Tally of outbound payments grouped by payment type

A tally of your outbound payments grouped by payment type, weighted by transaction count.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Per-type counts + total
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "counts": {},
  "total": 0,
  "sampled": 0
}
GET/journal/audit/verify
Verify the company's tamper-evident audit chain

Re-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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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`.
Responses
StatusDescription
200The chain's verdict: intact (`ok: true`) or broken (`ok: false` + `brokenAt`)
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required
403A request naming another company.
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "ok": false,
  "length": 0,
  "genesisAnchored": false,
  "brokenAt": {
    "index": 0,
    "eventId": "string",
    "reason": "content-altered"
  },
  "purgeVerified": false
}
GET/journal/documents/{id}/raw-pain002
Download the bank's original pain.002 status report

Returns 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200The stored pain.002 XML, as the bank sent it (attachment)
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404No stored raw pain.002 for this document
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
GET/journal/reconciliation
Reconciliation view: match outbound payments to pain.002 and camt evidence

Returns 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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 integerquerynoPage size over the outbound payments.
cursor stringquerynoOpaque keyset cursor from a previous response's `nextCursor`.
Responses
StatusDescription
200Per-payment reconciliation + evidence
400A `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`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
}
GET/journal/events
Activity-log events (audit trail) for a company

Keyset-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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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 integerqueryno
cursor stringquerynoOpaque keyset cursor from a previous response's `nextCursor`. Mutually exclusive with `since`.
connectionId stringquerynoReturn 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 integerquerynoResume 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 stringquerynoReturn 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 stringquerynoReturn only events at or before this instant (inclusive). See `from`.
notify enumquerynoReturn 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 enumquerynoLanguage of each item's `display` text. `display` is derived at read time and the stored `message` is never changed.
Responses
StatusDescription
200Events newest-first
400A `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`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404`connection_not_found`: `connectionId` is not one of your connections
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
}
POST/inbound/payment-status
Parse a pain.002 status report into a normalised payment status (preview only, not stored)

Parses 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.

Credentials API key, sent as the X-API-Key header
Responses
StatusDescription
200The normalised payment status
400Empty body (`empty_body`), or not a pain.002 (`unrecognised_format`)
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
413The request body is larger than this operation accepts (`payload_too_large`).
422The file could not be parsed (`parse_failed`)
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
    }
  ]
}
POST/inbound/statement
Parse a camt.052/053/054, MT940/MT942 or CFONB 120 statement into normalised JSON (preview only, not stored)

Parses 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Normalised statement(s)
400Empty body or unrecognised format No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
413The request body is larger than this operation accepts (`payload_too_large`).
422Unparseable 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.
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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)

GET/approvals/pending
List payments awaiting approval

For 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).

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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 enumqueryno`pending` (the default) is the queue described above; `approved` or `rejected` is the company's decided trail.
cursor stringquerynoThe previous page's `nextCursor`: the page continues after that request.
after stringquerynoDeprecated alias of `cursor`; `cursor` wins when both are sent.
limit integerquerynoPage size, at most 500.
Responses
StatusDescription
200Pending approval requests
400A `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`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
}
GET/approvals/{id}
One approval request, with what it decides on

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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200The request
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404No such request in this organisation (or not one that names this ERP sign-in)
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
  }
}
GET/approvals/policies
List approval policies for a company

The 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Policies: the standard { items } envelope, one row per policy
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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
    }
  ]
}
GET/approvals/bank-rosters
Who may approve payments on each covered bank

One 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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 stringqueryyesComma-separated covered bank keys (repeatable).
Responses
StatusDescription
200One roster row per covered bank
400No bank keys named No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "items": [
    {
      "bankKey": "string",
      "policyId": "string",
      "policyName": "string",
      "builtIn": false,
      "bound": false,
      "approverUserIds": [
        "string"
      ],
      "requiredApprovals": 0,
      "sharedWith": [
        "string"
      ]
    }
  ]
}
GET/approvals/policy
The company's active approval policy

The 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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`.
Responses
StatusDescription
200The active policy (`policy`)
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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)

GET/accounts
List bank accounts for a company

Every 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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 integerqueryno
cursor stringqueryno
Responses
StatusDescription
200Accounts with latest balances
400A `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`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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
}
GET/accounts/statements
List imported camt.053 statements for an account

Per-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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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 stringqueryyes
limit integerquerynoStatements per page, counted before the page is extended to the end of its period. A page holds fewer than twice `limit`.
cursor stringquerynoThe previous answer's `nextCursor`. A malformed value is 400 `invalid_cursor`.
Responses
StatusDescription
200One 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`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404Unknown account id for this company
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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)

GET/webhooks
List webhook subscriptions

The 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Subscriptions
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Admin required: a session without the Admin role is refused (`forbidden`).
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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
  }
}
POST/webhooks
Create a webhook subscription (Admin)

Registers an HTTPS endpoint to receive signed event callbacks. Admin only. The subscription field is eventTypes (an array of event names); a typo like events is rejected (400) rather than silently subscribing to everything, 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.

Credentials API key, sent as the X-API-Key header
Request body required
PropertyTypeRequiredDescription
companyIdstringyes
urlstringyes
eventTypesenum[]noEvent 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.
descriptionstringno
transportenumno
emailTostringno
filterPredicateobject | nullno
Example
{
  "companyId": "string",
  "url": "string",
  "eventTypes": [
    "*"
  ],
  "description": "string",
  "transport": "https",
  "emailTo": "string",
  "filterPredicate": {
    "bankKey": "string",
    "currency": "string",
    "minAmount": 0,
    "maxAmount": 0
  }
}
Responses
StatusDescription
201Subscription created (returns the signing secret once)
400The 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`.
401Authentication required: the credential is missing, invalid or expired.
403`capability_disabled`: webhooks are not enabled for this organisation
413The request body is larger than this operation accepts (`payload_too_large`).
422The 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.
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (201)
{
  "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"
}
GET/webhooks/{id}
Get a single webhook subscription

Returns one webhook subscription's detail (includes deliverySequence).

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200The webhook subscription
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Admin required: a session without the Admin role is refused (`forbidden`).
404Not found
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
    ]
  }
}
DELETE/webhooks/{id}
Delete a webhook subscription (Admin, soft delete)

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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
confirm stringqueryyesThe endpoint's own id. Always required for this route.
companyId stringquerynoThe 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.
Responses
StatusDescription
200Deleted
400Confirmation required: re-send with ?confirm=<id> No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Admin required: a session without the Admin role is refused (`forbidden`).
404Not found
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "removed": false
}
POST/webhooks/{id}/enable
Enable a disabled webhook (Admin)

Turns 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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.
Request body required
PropertyTypeRequiredDescription
companyIdstringno
Example
{
  "companyId": "string"
}
Responses
StatusDescription
200Webhook enabled
400The 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`.
401Authentication required: the credential is missing, invalid or expired.
403Admin required: a session without the Admin role is refused (`forbidden`).
404Not found
413The request body is larger than this operation accepts (`payload_too_large`).
422The 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.
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
    ]
  }
}
POST/webhooks/{id}/disable
Disable a webhook without deleting it (Admin)

Stops events reaching the endpoint without deleting it or its delivery history. Turn it back on with POST /webhooks/{id}/enable.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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.
Request body required
PropertyTypeRequiredDescription
companyIdstringno
Example
{
  "companyId": "string"
}
Responses
StatusDescription
200Webhook disabled
400The 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`.
401Authentication required: the credential is missing, invalid or expired.
403Admin required: a session without the Admin role is refused (`forbidden`).
404Not found
413The request body is larger than this operation accepts (`payload_too_large`).
422The 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.
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
    ]
  }
}
POST/webhooks/{id}/test
Fire a signed test event to the webhook URL (Admin)

Sends a signed ping to the endpoint and returns the delivery result.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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.
Request body required
PropertyTypeRequiredDescription
companyIdstringno
Example
{
  "companyId": "string"
}
Responses
StatusDescription
200Delivery result of the test event
400The 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`.
401Authentication required: the credential is missing, invalid or expired.
403Admin required: a session without the Admin role is refused (`forbidden`).
404Not found
413The request body is larger than this operation accepts (`payload_too_large`).
422The 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.
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "ok": false,
  "queued": false
}
GET/webhooks/{id}/deliveries
List delivery attempts for a webhook (Admin)

Per-attempt delivery log so no event is silently lost. Newest first, keyset-paginated: pass cursor = the previous page's nextCursor to continue. limit defaults to 50, max 200.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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 stringquerynoThe previous page's `nextCursor`, opaque. A malformed one is 400 `invalid_cursor`.
limit integerqueryno
Responses
StatusDescription
200A page of delivery attempts
400A `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`.
401Authentication required: the credential is missing, invalid or expired.
403Admin required: a session without the Admin role is refused (`forbidden`).
404Webhook not found
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
}
POST/webhooks/{id}/deliveries/{deliveryId}/redeliver
Replay a single delivery (Admin)

Re-enqueues the original event payload, as a new delivery attempt (a new row; the original failure record is preserved). A fresh signature is computed at send time. 404 if the delivery is unknown or not in a failed/delivered state.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
deliveryId stringpathyes
companyId stringquerynoThe 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.
Responses
StatusDescription
200Re-enqueued
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Admin required: a session without the Admin role is refused (`forbidden`).
404Delivery not found / not redeliverable
413The request body is larger than this operation accepts (`payload_too_large`).
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "redelivered": false,
  "deliveryId": "string"
}
POST/webhooks/{id}/redeliver-failed
Bulk-replay failed deliveries since a timestamp (Admin)

Re-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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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 stringqueryyesISO timestamp: only failed deliveries created at/after this are replayed.
cursor stringquerynoThe previous call's `nextCursor`, opaque, to continue draining. A malformed one is 400 `invalid_cursor`.
Responses
StatusDescription
200Re-enqueued count + drain cursor
400A 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`.
401Authentication required: the credential is missing, invalid or expired.
403Admin required: a session without the Admin role is refused (`forbidden`).
404Webhook not found
413The request body is larger than this operation accepts (`payload_too_large`).
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "count": 0,
  "nextCursor": "string"
}
POST/webhooks/{id}/re-enable
Re-enable an auto-disabled webhook (Admin)

For 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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.
Responses
StatusDescription
200Re-enabled
400Webhook is not disabled No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Admin required: a session without the Admin role is refused (`forbidden`).
404Webhook not found
413The request body is larger than this operation accepts (`payload_too_large`).
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "webhookId": "string",
  "enabledAt": "string"
}
POST/webhooks/{id}/rotate-secret
Rotate the signing secret with an overlap window (Admin)

Generates a new signing secret and returns it once. The previous secret is retained as a 'pending' secret and both signatures verify during the overlap window (overlapHours, default 24, max 72), so the receiver can update its key without dropping in-flight deliveries. Outbound deliveries are immediately signed with the new secret. After the window (housekeeping) the old secret is dropped.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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.
Request body
PropertyTypeRequiredDescription
companyIdstringno
overlapHoursnumbernoHow long the old secret stays valid.
Example
{
  "companyId": "string",
  "overlapHours": 0
}
Responses
StatusDescription
200Rotated: newSecret shown once
400Invalid body No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Admin required: a session without the Admin role is refused (`forbidden`).
404Webhook not found
413The request body is larger than this operation accepts (`payload_too_large`).
422The 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.
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "webhookId": "string",
  "newSecret": "string",
  "overlapUntil": "string"
}

User Management

Create, invite, and manage users within a company.

GET/users
List users in a company

Returns all users in the company (name, email, roles, status).

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Company users
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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"
        }
      ]
    }
  ]
}
POST/users
Invite a company user

Company 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.

Credentials API key, sent as the X-API-Key header
Request body required
PropertyTypeRequiredDescription
companyIdstringyes
emailstringyes
namestringyes
rolesstring[]yes
codestringnoThe acting admin's 2FA TOTP code (when step-up applies; an `admin` or `approver` role).
backupCodestringnoA 2FA backup code, in place of `code`.
Example
{
  "companyId": "string",
  "email": "approver@acme.com",
  "name": "Bob Approver",
  "roles": [
    "approver"
  ],
  "code": "string",
  "backupCode": "string"
}
Responses
StatusDescription
201User invited
400Invalid 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`.
401Authentication required: the credential is missing, invalid or expired.
403Company admin required (`forbidden`); an API key creating an `admin` or `approver`.
409The email address already belongs to a user of this organisation (`conflict`)
413The request body is larger than this operation accepts (`payload_too_large`).
422Step-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)
429Too many wrong 2FA codes (`rate_limited`); see `Retry-After`
503An invite with no email transport to deliver it (`invites_unavailable`), refused before the code is checked so a single-use code is not spent
Example response (201)
{
  "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"
}
GET/users/{id}
Get a single user

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 admin role receive the whole record: settings, companyId and createdAt included.
  • Any other signed-in company member receives the narrower roster projection (id, email, name, roles, status, totpEnabledAt). The same six fields GET /users publishes. 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200The 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.
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
404Unknown user in this company
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "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

GET/dashboard/kpis
Home KPI tiles: exact company-wide payment figures for one calendar day

Counts 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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 stringqueryyesThe calendar day to window on, as YYYY-MM-DD. A value carrying a time is rejected.
timezone stringquerynoIANA zone the day is resolved in, e.g. Europe/Copenhagen. Defaults to UTC.
Responses
StatusDescription
200KPI aggregate for the requested day
400Missing 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`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "day": "string",
  "timezone": "string",
  "yesterdayCount": 0,
  "pendingCount": 0,
  "execCount": 0,
  "execCountByCurrency": {},
  "rejectedOpenCount": 0,
  "todayByStatus": [
    {
      "status": "string",
      "currency": "string",
      "count": 0,
      "bankKeys": [
        "string"
      ]
    }
  ],
  "execMoney": {},
  "awaitMoney": {}
}
POST/bank-coverage-requests
Ask BankConnector to add coverage for a bank we don't support yet

Any 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.

Credentials API key, sent as the X-API-Key header
Request body required
PropertyTypeRequiredDescription
querystringyesBIC code or bank name.
emailobjectno
Example
{
  "query": "string",
  "email": "string"
}
Responses
StatusDescription
201Request recorded
400Empty query
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
413The request body is larger than this operation accepts (`payload_too_large`).
422The 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.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (201)
{
  "ok": false,
  "request": {
    "id": "string",
    "query": "string",
    "createdAt": "string"
  }
}
GET/banks/{bankKey}/test-scenarios
Available one-click Test Payment scenarios for a bank

Lists the canned test-payment scenarios this bank can send (domestic, sepa, international, urgent), tailored to its country and offered payment types: each with its label, amount, currency and the chosen payment type.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
bankKey stringpathyes
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Offered scenarios
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "scenarios": [
    {
      "id": "domestic",
      "label": "string",
      "description": "string",
      "amount": "string",
      "currency": "string",
      "creditorName": "string",
      "paymentType": "string"
    }
  ]
}

System

Health, readiness and FX reference rates

GET/health
Liveness check

Always 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.

Credentials none, this call is public
Responses
StatusDescription
200Server is running
Example response (200)
{
  "status": "string",
  "warnings": [
    "string"
  ]
}
GET/ready
Readiness check

Readiness 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.

Credentials none, this call is public
Responses
StatusDescription
200Ready
503Not 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`.
Example response (200)
{
  "ready": false
}
GET/fx/rates
Indicative FX reference rates

Global (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 }.

Credentials API key, sent as the X-API-Key header
Responses
StatusDescription
200Cached EUR-base rates
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "base": "string",
  "rates": {},
  "asOf": "string"
}

Tenancy

GET/company/approval-settings
Read this organisation's approval-governance settings

Readable 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).

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Approval settings
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "approvalSettings": {
    "forbidSelfApproval": false,
    "updatedAt": "string",
    "updatedBy": "string"
  }
}
GET/company/payee-verification
Read payee verification on this organisation's files of several payments

Readable 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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`.
Responses
StatusDescription
200The payee-verification setting
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "payeeVerification": {
    "batches": "opt-in",
    "updatedAt": "string",
    "updatedBy": "string"
  }
}
GET/partner-access/requests
The CUSTOMER's own incoming partner-access requests

Every 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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`.
Responses
StatusDescription
200Incoming requests
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "items": [
    {
      "id": "string",
      "orgId": "string",
      "firmId": "string",
      "firmName": "string",
      "requestedBy": "string",
      "requestedByName": "string",
      "requestedLevel": "status",
      "status": "pending",
      "createdAt": "string",
      "decidedAt": "string",
      "decidedBy": "string"
    }
  ]
}
GET/partner-access/firms
The partner firms that reach this organisation, and the named people they reach it through

Each 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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`.
Responses
StatusDescription
200The firms with access
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "items": [
    {
      "firmId": "string",
      "firmName": "string",
      "level": "status",
      "grantedAt": "string",
      "grantedBy": "string",
      "consultants": [
        {
          "userId": "string",
          "name": "string",
          "email": "string",
          "role": "partner-admin"
        }
      ]
    }
  ]
}
GET/partner-access/firm-choices
The partner firms a customer can give access to

Every 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.

Credentials API key, sent as the X-API-Key header
Responses
StatusDescription
200The partner firms
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "items": [
    {
      "id": "string",
      "displayName": "string"
    }
  ]
}
GET/partner-access/deleted-firms
The deleted partner firms that reached this organisation

Each 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
companyId stringquerynoThe 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`.
Responses
StatusDescription
200The deleted partner firms
400No organisation to act on (`scope_required`): an API key names none of its own, so send `companyId`.
401Authentication required: the credential is missing, invalid or expired.
403Forbidden: the credential's role, scope or grant does not allow this operation.
429Too many requests from this network (`rate_limited`); wait `Retry-After` seconds.
Example response (200)
{
  "items": [
    {
      "firmId": "string",
      "firmName": "string",
      "deletedAt": "string",
      "introduced": false
    }
  ]
}
PUT/users/{id}/roles
Replace a user's role set

PUT = 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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
Request body required
PropertyTypeRequiredDescription
companyIdstringyes
rolesstring[]yes
codestringno2FA TOTP code (when step-up applies).
backupCodestringnoA 2FA backup code, in place of `code`.
Example
{
  "companyId": "string",
  "roles": [
    "string"
  ],
  "code": "string",
  "backupCode": "string"
}
Responses
StatusDescription
200Updated user
400The 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`.
401Authentication required: the credential is missing, invalid or expired.
403The caller may not make this change: an API key granting Admin or Approver, or changing an Admin's roles
404No such user in this company (`not_found`), answered before any code is checked so a single-use code is not spent
413The request body is larger than this operation accepts (`payload_too_large`).
422Step-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)
429Too many wrong 2FA codes (`rate_limited`); see `Retry-After`
Example response (200)
{
  "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"
  ]
}
POST/users/{id}/reinvite
Re-send a user's invite (command)

Re-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.

Credentials API key, sent as the X-API-Key header
Parameters
NameInRequiredDescription
id stringpathyes
Request body required
PropertyTypeRequiredDescription
companyIdstringno
Example
{
  "companyId": "string"
}
Responses
StatusDescription
200Invite re-sent
400The 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`.
401Authentication required: the credential is missing, invalid or expired.
403Admin required: a session without the Admin role is refused (`forbidden`).
404No pending invite for this user in this company (`not_found`)
413The request body is larger than this operation accepts (`payload_too_large`).
422The 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.
429Too many requests from this caller or this network (`rate_limited`); wait `Retry-After` seconds.
503No email transport to deliver the invite (`invites_unavailable`)
Example response (200)
{
  "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"
}