sandbox.bankconnector.com API Reference

API versioning & compatibility

BankConnector's HTTP API is versioned by date, via the BankConnector-Version header, never a /v1 path prefix. Pin a version and the contract stays stable for you as the platform evolves underneath it.

BankConnector-Version: 2026-06-01

Every response echoes back the version it was served under (including error responses), so you can confirm what you're pinned to at a glance.

Omit the header and you get 2026-06-01, the oldest version we serve, not the newest. That is deliberate: an integration that never pinned anything is one we must not move underneath. Send the header. Any date you send resolves to the newest version published at or before it, and that resolved date is what comes back in the response.

Compatibility commitment

We treat the API as a long-lived contract:

  • A pinned version keeps behaving the way it did when you integrated it. We do not make breaking changes to a version you've pinned.
  • Additive changes (new optional request fields, new response fields, new endpoints) may appear within a version without notice. Build tolerant clients (ignore unknown fields, branch on code not message) so these never break you.
  • Breaking changes ship under a new dated version. The previous version keeps working, and you move on your own schedule after advance notice; you're never forced to migrate on our timeline.

This is why we recommend pinning BankConnector-Version explicitly in production rather than relying on the default.

Current version: 2026-10-21

  • A connection request refused for the connection's state is 409 invalid_state, not validation_failed: the request was fine, and the connection needs another step first. See 2026-10-21 below.
  • A request body we read but cannot use is 422, not 400, on every route, with each problem on its field in error.details.issues, and a field the route does not read is refused rather than dropped. See 2026-10-20 below.
  • A payment we cannot process is 422, not 400. 400 means we could not read your request at all (not JSON, a bad header); 422 means the request was fine and the payment inside it is not something we can accept, with the reasons in error.details.issues. See 2026-08-25 below, and Errors & Retries.
  • Canonical input schema 1.1 (additive over 1.0: structured postalAddress, regulatoryReporting, and purposeCode / localInstrument / categoryPurpose overrides; see Core Concepts).
  • Object error envelope: { error: { code, message, details? } } on every non-2xx response (see Errors & Retries).
  • Standard list envelope: { items, nextCursor?, total? } across all list endpoints (see Pagination).
  • One word for "you have pulled this document": collected. On the journal read surfaces (GET /journal/list and GET /journal/documents) each item carries collected / collectedAt, the /journal/list envelope carries an uncollected count, and the filters are ?uncollectedOnly=true and ?collected=true|false. See Receiving Results.

Versions

Four versions can be pinned with BankConnector-Version. Everything else on this page is an additive change that applies to all of them.

2026-10-21

Pin this version to tell a refused request apart from a connection that is not ready. A connection request can be refused because of what it sends, or because the connection is not at a point that allows it yet: a setup step or field not done, keys not sent, live when the action needs it deactivated, or a channel the action does not apply to. On this version the second is 409 invalid_state.

Refusal2026-06-01, 2026-08-252026-10-202026-10-21
the connection is not ready for the request400 validation_failed422 validation_failed409 invalid_state
a value cannot be used: missing or malformed in the request, or stored but malformed400 validation_failed422 validation_failed422 validation_failed (unchanged)

The message is the same on every version and names the step to take first. The routes are POST /connections, the connection setup actions under /connections/{id}, the certificate and credential uploads, and GET /connections/{id}/ebics-letter. On POST /connections/{id}/ws-certificate, a connection that does not use Web Services is 409 invalid_state and a family that is not the connection's own is 422 on this version; both stay 400 on every earlier version.

What to do:

  • If you branch on the status of a connection action, handle 409 invalid_state: show the message, and do not retry the same request until the step it names is done.
  • Nothing forces you to move. Keep sending 2026-10-20 and you keep 422.

2026-10-20

Pin this version to get the same line for every other request body. A body that is valid JSON but not what the route takes (a wrong type, a value out of range) is 422 validation_failed, and a field the route does not read is refused with 422 instead of being dropped, so a misspelt field is an answer rather than a success that changed nothing.

Request2026-06-01, 2026-08-252026-10-20
a body that fails the route's schema400422
a body with a field the route does not readaccepted, the field dropped422, the field named
a body that is not JSON400400 (unchanged)

On every version, each refusal lists its problems in error.details.issues, one { "path", "message" } per problem, where path names the field (rules[0].amountFrom). companyId is accepted on every body; a platformId is refused 400 invalid_parameter on every version on a route that needs a credential (see October 2026). A public route (/auth/login, /signup and the like) refuses it like any other unknown body field. POST /payment-types/suggest keeps reading a whole payment and ignoring the fields it does not use.

What to do:

  • Send only the fields each route documents before you move your pin.
  • If you branch on the status, handle 422 wherever you handle a refused write.
  • Nothing forces you to move. Keep sending 2026-08-25 and you keep the 400 and the dropped field.

2026-08-25

Pin this version to get a clear line between the two client errors. A payment body that arrives as valid JSON but is not a valid canonical payment (a missing required field, a wrong type, a null where an object belongs) is 422 validation_failed, with every problem listed in error.details.issues. On 2026-06-01 the same defect is 400 on some routes and 422 on others.

Request2026-06-012026-08-25
POST /journal/payments, a transaction with no creditor account400422
POST /journal/payments/preview, same body400422
POST /convert/{bank}, body null, 42, [] or {"payments":[null]}400422
POST /convert-async/{bank}, same bodies400422
POST /validate/{bank}, same bodies400200, valid: false
a payment that parses but breaks a rule422422 (unchanged)
a body that is not JSON at all400400 (unchanged)

The boundary is one sentence: 400 means we could not read your request, and 422 means we read it and the payment is not one we can process. Neither is retryable as sent.

POST /validate/{bank} is a report endpoint, so it reports a schema failure inside its 200, as { "valid": false, "issues": [...] }, the same way it reports every other problem. That lets it tell you in advance what POST /convert/{bank} will answer.

What to do:

  • If you branch on the status, handle 422 wherever you handle a rejected payment, and read 400 as "my request was malformed" only.
  • If you read error.details.issues, nothing changes: it is in the same place under both statuses and both versions.
  • If you treat a non-200 from POST /validate/{bank} as "could not validate", read valid instead. This is the one change that could make a rejection look like a success, so it is the one to search your code for.
  • Nothing forces you to move. Keep sending 2026-06-01 and you keep the 400 answers.

2026-06-01

The baseline, and the version you get when you send no header. Contract fixes made before the first customer pinned a version were made in place, not as new versions, so this version reads the same for every integrator: lists use the standard envelope, errors use the object envelope, and the journal calls it collected where an earlier snapshot said retrieved. On this version a payment that fails the schema check is 400 on POST /convert and POST /convert-async, for every shape of failure.

Contract changes

This page lists the changes to the HTTP contract that you might need to act on or would want to use, in one short overview per month. It leaves out internal work and small additions, because those do not change what you integrate against. If you pin a specific BankConnector-Version, watch this page.

Each month's overview lists removals first. Small additions, such as a new optional field, appear in the API Reference and are not repeated here.

October 2026

October added follow-up for payments the bank has not settled, and a verification-only file for checking payees.

  • New: a platform's own web app or mobile app can sign a person in to approve payments. An application can register exact https redirect URIs or a private-use scheme beside the add-on loopback, and a web app can be a confidential client with a client secret, sent to POST /oauth/token and POST /oauth/revoke by HTTP Basic or as client_secret (a missing or wrong one is 401 invalid_client). See From a web or mobile app.
  • Added: each row of GET /journal/list and GET /journal/documents carries environment ("test" or "production"). A bank's test system can send statements for a real account; an integration that books statements skips the "test" rows.
  • Added: transport: "teams" on POST /webhooks posts each event into a Microsoft Teams chat or channel as a card. A webhook's transport can now read "teams".
  • Changed: a "*" webhook subscription (or one created without eventTypes) receives the events in the event catalog and no others. To keep receiving an event the catalog does not list, name it in eventTypes.
  • Changed: a request to a route that needs a credential that states platformId (in the query string or the JSON body) answers 400 invalid_parameter, naming the parameter, on every version. A public route refuses it like any other unknown body field. Name the organisation with companyId alone, and send no platformId, not even an empty one.
  • Changed: an EBICS setup step whose call to the bank fails (POST /connections/{id}/ebics-hev, ebics-send-ini, ebics-fetch-hpb and the other steps that call the bank) answers 502 bank_unavailable with error.details.nextSteps, including a host name that does not exist; it answered 500 internal_error.
  • Changed: POST /approver-identity refuses a fødselsnummer that is not valid, or a District user ID that is empty or over 35 characters, as validation_failed (422 on 2026-10-20, 400 before it), as a refused request body is. It answered 422 on every version.
  • Changed: POST /company/require-2fa refuses a body without enabled as validation_failed (422 on 2026-10-20, 400 before it). It read a missing enabled as false and turned the requirement off. Send "enabled": false to turn it off.
  • Changed: POST /connections/{id}/approvers answers an approval-governance refusal with its own code (409 policy_change_in_flight, 422 policy_quorum_shortfall, 400 policy_roster_invalid), as POST /approvals/bank-rosters does; it answered 500 internal_error.
  • New: GET /banks/{bankKey}/sftp-naming answers the server, logins, folders and payment file name a TietoEvry bank's agreement answers give (SpareBank 1, Eika and the other savings banks on TietoEvry's shared SFTP), before anything is saved. A bank whose setup guide your organisation has not been given answers 403 bank_guide_access_required, as POST /connections does.
  • New: GET /profiles/members lists the Sparkassen and cooperative banks (Volksbanken, Raiffeisenbanken) the German group profiles serve, each with its town, BIC and bank codes and bankKey, the profile to connect it under. Match a customer's bank name, BIC, bank code or IBAN against it to find the bank key, and save the member with memberBankCode on PATCH /banks/{bankKey}/settings.
  • Changed: PATCH /banks/{bankKey}/settings refuses a memberBankCode that would change the member bank of a group profile the company has a connection on, or a setup in progress for another member, with 409 member_bank_locked. The first choice, and sending the stored member again, pass.
  • New version: 2026-10-21. A connection request refused because the connection is not ready for it (a setup step not done, live when it must be deactivated, the wrong channel) is 409 invalid_state; a value that cannot be used stays 422 validation_failed. See 2026-10-21.
  • New version: 2026-10-20. A request body that is not a payment and fails its route's schema is 422, and a field the route does not read is refused, naming the field. On every version, a refused body lists each problem in error.details.issues as { path, message }, and the message writes an array position in brackets (rules[0].amountFrom). See 2026-10-20.
  • Changed: a refusal of a user or connection request carries one code wherever it is raised. POST /users answers 409 conflict for an email address already registered (it was 400 bad_request). A connection route naming an unknown or deleted connection answers 404 connection_not_found, where some answered 400 validation_failed or 404 not_found. GET /connections/{id}/ebics-letter before the subscriber keys exist answers 400 validation_failed (it was 404). On 2026-10-20 a connection's validation_failed refusal is 422, as a body refusal is.
  • New: the Uniconta add-on can ask for its install and the signed-in person's own approve sign-in in one consent. GET /oauth/authorize lists the second half as identityScopes, and the loopback carries identity_code beside code when the person may approve there, or error_reason when they may not. Each code redeems at POST /oauth/token with the same code_verifier.
  • Changed: POST /signup's erpPartner links a partner only when the general signup lists it (the partner GET /signup/erp-partner/uniconta/{pid} names); any other number links none, as a signup with no partner. partnerAccess beside a number that links none is refused 422 validation_failed (details.field: partnerAccess), where it was required and ignored. Send partnerAccess only for a partner that route names.
  • New: an ERP add-on can open the signup page instead of sending POST /signup from its own form. The page's link takes the company's partner by its Uniconta number (#erpPartner.uniconta=<pid>), which the page names and asks about through the new GET /signup/erp-partner/uniconta/{pid} when the general signup lists that partner (otherwise the customer picks a partner, or none, as on the general signup), and the add-on's pending sign-in (clientId, redirectUri, scope, state, codeChallenge, codeChallengeMethod, erpCompanyId), so the owner lands on the consent screen after verifying their email.
  • New: an organisation has a product, direct-api or erp-addon (with its ERP), chosen at signup (product, productErp on POST /signup) and read at GET /company/product. An Admin changes it with PATCH /company/product until the organisation chooses a package; after that the route answers 409 package_chosen, and on an Embedded organisation 409 product_embedded.
  • Changed: POST /connections/{id}/ebics-params answers 400 validation_failed to a new signatureVersion once the EBICS connection's keys went to the bank, and saves nothing. It saved the version before, and the next upload was signed in a version the bank had not registered. Set the version before you send the keys.
  • Changed: POST /connections/{id}/server-info refuses an SFTP host-key pin that is not a SHA-256 fingerprint (hostFingerprint, downloadHostFingerprint), as 422 validation_failed on 2026-10-20 and 400 before it, and saves nothing. It saved an MD5 or half-typed pin before, and every connection to the bank then failed. Send SHA256: plus base64 or 64 hex characters; either is stored as SHA256: plus unpadded base64.
  • Changed: GET /webhooks/{id}/deliveries and POST /webhooks/{id}/redeliver-failed hand out a different nextCursor (still opaque), refuse a malformed cursor with 400 invalid_cursor, and end with nextCursor: null on the last page, even when it is exactly full. A cursor issued before this change is malformed, and a cursor built from a delivery's id is refused: pass nextCursor back as it came. redeliver-failed refuses a since that is not an ISO-8601 date or time, or not a real one (2026-02-30), with 400 invalid_since.
  • Changed: GET /accounts, GET /company/api-keys, GET /installs, GET /me/organisations, GET /partner/customer-logs, GET /approvals/pending and GET /approvals/oversight hand out a different nextCursor (still opaque), and refuse a malformed cursor with 400 invalid_cursor. A cursor issued before this change is malformed, and a cursor built from an item's id is refused: pass nextCursor back as it came. The two approval lists end with nextCursor: null on the last page, even when it holds exactly limit requests.
  • Renamed: four remittance warnings carry one code each, whatever bank or standard raises them, on every version: BANK_REMITTANCE_DOCUMENTS_AS_TEXT (was also BANK_SE_, BANK_SEPA_, BANK_NPC_ and BANK_NL_REMITTANCE_DOCUMENTS_AS_TEXT), BANK_REMITTANCE_ADDITIONAL_INFO_NOT_SENT (was also BANK_SE_, BANK_SEPA_ and BANK_NPC_REMITTANCE_ADDITIONAL_INFO_NOT_SENT, and BANK_SEPA_REMITTANCE_TEXT_NOT_SENT on additionalInfo), BANK_REMITTANCE_TEXT_NOT_SENT (was BANK_SE_ and BANK_NPC_REMITTANCE_TEXT_NOT_SENT, and BANK_SEPA_REMITTANCE_TEXT_NOT_SENT on the text or documents) and BANK_REMITTANCE_TEXT_JOINED (was also BANK_SE_ and BANK_SEPA_REMITTANCE_TEXT_JOINED). The message, path and severity are unchanged. If you match on an old code, match on the new one.
  • Changed: a messageId, paymentId, endToEndId or instructionId made only of whitespace (spaces, tabs, line breaks) or invisible characters (such as the zero width space or the soft hyphen) is refused as a schema failure naming the field, because the payment file would carry an id that reads as empty. /convert answered 200 with a blank id element before. Spaces and invisible characters inside or around other characters are kept as sent.
  • Changed: a messageId, paymentId, endToEndId or instructionId made only of emoji or control characters is refused as a schema failure naming the field, because the payment file cannot carry those characters and the id would be empty. /convert answered 200 with an empty id element before, and POST /journal/payments refused it later as XSD_INVALID. An id with emoji beside other characters is accepted and written without the emoji.
  • Changed: a list call with a value that is not allowed (type, direction or status outside its set) or a limit that is not a number answers 400 invalid_parameter, naming the parameter, instead of an empty or unfiltered page. GET /approvals/pending and GET /approvals/oversight page with cursor, as every other list does; after still works. To poll for documents use GET /journal/list?uncollectedOnly=true.
  • New status: the certificate renewal routes (POST /connections/{id}/renew-danske and the five others) answer 409 renewal_in_progress while another renewal of the same connection is running. Nothing reached the bank; read the connection again after a minute for the new expiry.
  • Now in the API reference, already served: GET /approvals/policy (the company's active approval policy), GET /journal/documents/{id}/raw-pain002 (the bank's original pain.002 status report) and POST /payment-types/suggest (the payment type that fits a payment).
  • Removed: the twelve Web Services certificate paths, which now return 404: POST /connections/{id}/activate-danske, /activate-nordea, /activate-op, /activate-samlink, /activate-crosskey, /activate-bankconnect, /renew-danske, /renew-nordea, /renew-op, /renew-samlink, /renew-crosskey and /renew-bankconnect. One path does both acts for every bank: POST /connections/{id}/ws-certificate with action (activate or renew) and family, the kind that GET /banks/{bankKey}/setup-info serves for the bank. The fields and the errors are the same, except that an unknown connection now answers 404 connection_not_found and a family that is not the connection's own is refused with 400 validation_failed. Only a signed-in Admin calls it, as before.
  • Changed: GET /accounts/statements answers one page of the list envelope: items replaces statements, and nextCursor (pass it back as ?cursor=, with ?limit= up to 500) points at the next older page. account and gaps stay; each page's gaps are the ones whose later statement is on that page. Follow nextCursor until it is null to read the whole account.
  • Removed: POST /partner/firm/members/{userId}/remove now returns 404. A partner firm's administrator removes a person with DELETE /partner/firm/members/{userId}, which takes no body and answers as before.
  • Changed: a list's last page carries nextCursor: null even when it holds exactly limit items, on GET /journal/documents, /journal/list, /journal/events (paged by cursor) and /journal/reconciliation. GET /bank-coverage-requests refuses a malformed cursor with 400 invalid_cursor, and a cursor it issued before this change is malformed.
  • Removed: GET /version, which answers as any unknown path does. The API reference's version is the contract date you send in BankConnector-Version.
  • Removed: the platform paths for erasing a company, placing a legal hold and renaming a company now return 404. To erase an organisation or hold its data, ask us. An Admin renames their own organisation with PATCH /company/name, and GET /company/legal-identity needs a signed-in Admin (an API key gets 403).
  • Removed field: guide.fieldCopy and bankConnect.fieldCopy on the GET /banks/{bankKey}/setup-info response. Each entry of fields[] carries the same label, placeholder and help, in the order the enrolment form asks for them.
  • Removed field: approval.limitEquivalence on the POST /journal/payments response. The response's approval is the request as GET /approvals/pending shows it.
  • Removed field: grantsRevoked on the DELETE /connections/{id} response, the granted value of reachableBanks[].access on GET /dashboard, and the webhook event type connection.grant-withdrawn. A bank connection belongs to one organisation and is not shared with another.
  • Removed field: platformId, from every response and from the webhook envelope, on every version. Key on companyId, which names your organisation. On a route that needs a credential, a request that states platformId is refused 400 invalid_parameter (see the first October entry).
  • Renamed: POST /connections/{id}/platform-bank-key is POST /connections/{id}/registry-bank-key (the old path returns 404), a connection's usePlatformBankKey is useRegistryBankKey, and platformBankKey on GET /connections/{id} is registryBankKey. Each still means BankConnector's copy of the bank's PGP key, and each connection keeps the choice it holds.
  • Removed: an approval policy has no versions. It keeps one id and is changed in place, so version, predecessorId and supersededById are gone from the policy object and status is draft or active. POST /approvals/policies/{id}/propose and POST /approvals/changes/{changeId}/submit return 404, a pending policy change is the kind policy-edit with its proposed rule in proposed, and the error code policy_superseded is retired.
  • Renamed: 36 validation issue codes take their level's prefix, BANK_ on a bank issue and PAYMENT_TYPE_ on a payment-type issue, on every version. Each keeps its old spelling after the prefix (NEMKONTO_DKK_ONLY becomes BANK_NEMKONTO_DKK_ONLY, ACH_ROUTING_REQUIRED becomes PAYMENT_TYPE_ACH_ROUTING_REQUIRED), and only the prefixed spelling is sent. If you match on any of these, match on the prefixed code: ACH_ROUTING_FORMAT, ACH_ROUTING_REQUIRED, CH_QR_REFERENCE_CHECKDIGIT_INVALID, CH_QR_REFERENCE_REQUIRED, EFT_CAD_ONLY, EFT_CREDITOR_ACCOUNT_REQUIRED, EFT_ROUTING_REQUIRED, BANK_OFFERS_NO_PAYMENT_TYPES, ING_NL_CTGYPURP_IGNORED, NVB_INITIATING_PARTY_NAME_OR_ID, TRUIST_LCLINSTRM_REQUIRED, USBANK_DEBTOR_AGENT_ID_REQUIRED, BANKCONNECT_BEC_NON_BANKING_DAY_REJECTED, CREDITOR_REFERENCE_TYPE_MISSING, DE_AXZ_LOCAL_INSTRUMENT_DROPPED, DE_CCU_CHARGE_BEARER_SUBSTITUTED, DE_CCU_LOCAL_INSTRUMENT_PROPRIETARY_DROPPED, DK_ADDRESS_COUNTRY_ONLY_DROPPED, DK_ADDRESS_ULTIMATE_PARTY_DROPPED, DK_ADDRESS_ULTIMATE_PARTY_OMITTED, DK_FIK_KORTART_NOT_OFFERED, FIXED_CREDITOR_AGENT_COUNTRY_OVERRIDDEN, FI_ADDRESS_HYBRID_TOO_MANY_LINES, FI_INVOICES_WITHOUT_CREDIT_NOTE_NOT_ERI, FR_ADDRESS_LINES_ONLY, NEMKONTO_DKK_ONLY, NEMKONTO_DOMESTIC_ONLY, NL_ADDRESS_LINES_ONLY, PROFILE_HMRC_RTI_REFERENCE_NOT_SENT, PROFILE_SERVICE_USER_NUMBER_CONFLICT, PROFILE_SERVICE_USER_NUMBER_NOT_SENT, PROFILE_SERVICE_USER_NUMBER_OVERRIDDEN_BY_ULTIMATE_DEBTOR, SEPA_INSTANT_PAIN_VERSION_UNSUPPORTED, SEPA_NON_EEA_DEBTOR_ADDRESS_INCOMPLETE, SEPA_NON_EEA_DEBTOR_COUNTRY_REQUIRED, SEPA_REMITTANCE_TEXT_NOT_SENT.
  • Renamed: 28 more bank issue codes take the BANK_ prefix the same way, on every version, so every bank issue code starts with BANK_ and every payment-type issue code with PAYMENT_TYPE_. If you match on any of these, match on the prefixed code: ADDRESS_DERIVATION_COUNTRY_MISSING, ADDRESS_DERIVATION_COUNTRY_UNSUPPORTED, ADDRESS_DERIVATION_POSTCODE_CONFLICT, ADDRESS_DERIVATION_TOO_MANY_LINES, ADDRESS_DERIVATION_TOWN_AMBIGUOUS, ADDRESS_DERIVATION_TOWN_NOT_FOUND, ADDRESS_DERIVATION_TOWN_TOO_LONG, ADDRESS_DERIVATION_VERSION_UNSUPPORTED, DEBTOR_ADDRESS_NOT_FILLED_COUNTRY_CONFLICT, DEBTOR_ADDRESS_NOT_FILLED_NO_REGISTERED_ADDRESS, DEBTOR_ADDRESS_NOT_FILLED_OTHER_PARTY, DENMARK_ADDRESS_COUNTRY_REQUIRED, DENMARK_ADDRESS_HYBRID_TOO_MANY_LINES, DENMARK_ADDRESS_TOWN_REQUIRED, DK_ADDRESS_COUNTRY_REQUIRED, DK_ADDRESS_LINE_TOO_LONG, DK_ADDRESS_TOO_MANY_LINES, DK_ADDRESS_TOWN_REQUIRED, FI_ADDRESS_COUNTRY_REQUIRED, FI_ADDRESS_TOWN_REQUIRED, FI_URGENT_LOCAL_INSTRUMENT_CONFLICT, FI_URGENT_SALARY_NOT_INSTANT, FI_URGENT_SEPA_INSTANT_UNAVAILABLE, SEPA_REMITTANCE_ADDITIONAL_INFO_NOT_SENT, SEPA_REMITTANCE_ADDITIONAL_INFO_UNSUPPORTED, SEPA_REMITTANCE_DOCUMENTS_AS_TEXT, SEPA_REMITTANCE_TEXT_AND_REFERENCE, SEPA_REMITTANCE_UNSTRUCTURED_REPEATED.
  • An application install's GET /journal/events returns only the events for its own payments and the organisation-wide approval and channel changes, on every version.
  • An application install holding statements:read also reads the events about the organisation's inbound statements on GET /journal/events: file.received, now with the file's type and masked accountIban, and the statement alerts. Poll for file.received to import a statement when it arrives.
  • An application install's journal document reads (GET /journal/documents, /journal/list, /journal/reconciliation, /journal/payment-types, /journal/payments/batch) return only the payments it submitted, on every version. Another company's payment by id is a 404.
  • An ERP add-on that sends erp_company_id at pairing keeps its company's payments across a reconnect or a second PC: every install paired for that company reads the payments the others sent and the events about them, so a payment sent before a reconnect still gets its answer. Send erp_company_id at every pairing.
  • New field to handle: reissuedBy: "other_sender" on refusals.resends[] of GET /journal/documents/{id}/payments. An application install reads it when another sender of the organisation (another ERP company's install, a person in the web app or an API key) re-issued one of its refused lines, and the element then has no documentId, journalNo or resendEndToEndId. state and resolved still say what became of the line: a resolved line was executed by the bank, so do not re-issue it again.
  • New value to handle: matched_other_sender in entryRecon[].state on GET /journal/documents/{id}. An application install reads it for a statement debit that paid a payment of the organisation it did not send (another ERP company's install, a person in the web app or an API key sent it), and the element names nothing of that payment. Treat it as settled, and not by you.
  • A partner and a customer can be one person with one sign-in. POST /signup and POST /partners/register accept an address the other already uses, and the person confirms with the password they have. GET /billing/subscription says why an organisation is on the Free plan in freePlanSource.
  • New status to handle: POST /signup needs the company register to find the company. A number the register does not know, or a company it lists as inactive, is refused with 422 on registrationNumber; a register that gives no answer is refused with 503 service_unavailable naming the number's field, and the same signup can be sent again in a moment. A country with no register lookup still signs up with the name and address as sent. Send the lookup's registerAnswer back with the signup.
  • New value to handle: accessReason: disconnected on GET /installs. Revoking an install's refresh token at POST /oauth/revoke ends the install, and it is listed as revoked with that reason. Revoke the old refresh token when a new connection replaces it, and on disconnect.
  • An ERP add-on can name the company it pairs for with erp_company_id on GET /oauth/authorize. Every install paired for that company shares one messageId check, so two companies of one organisation may use the same messageId, each recorded as its own payment.
  • New status to handle: a statement file stating an amount or credit/debit indicator we cannot read is refused with 422 incomplete_statement on POST /journal/ingest and POST /inbound/statement, and error.details.parseIssues names each field. Nothing from the file is stored. A file of this kind fetched from the bank sends no statement.imported; we keep it and deliver it once we can read it.
  • Required field: POST /partners/register takes partnerType (uniconta-partner, api-partner or software-vendor) and refuses an application without it with 422. A body carrying firmKind, firmKindOther, customerCount, erpSystems or erpSystemsOther is refused with 422 naming that field.
  • New status to handle: a partner access request on GET /partner-access/requests can read withdrawn. The partner was deleted from BankConnector while the request was open, so it is closed and gave no access.
  • New state to handle: the reconciliation state unconfirmed appears once a bank report is 5 banking days overdue. Check with the bank before paying again; see Receiving Results.
  • GET /journal/payments/batch?messageId= shows what BankConnector holds for a batch you sent and got no answer for. Only not-found means you may send it again, under a new messageId.
  • Send resendOf on a re-issued payment to link it to the refused one; the original batch's GET /journal/documents/{id}/payments then reports refusals.
  • POST /journal/payee-verifications checks payees with a verification-only file, and the results arrive as a file.received event.
  • New value to handle: a company register lookup's company.inactiveReasons can read insolvency (in receivership, administration, a voluntary arrangement or insolvency proceedings). A UK company number is looked up in Companies House on POST /signup/register-lookup and POST /partners/register/lookup.
  • GET /banks/live lists the banks your organisation has an active connection to, per environment, on the banks:read scope an application install holds.
  • GET /onboarding/status answers an application install holding the new onboarding:read scope, so an ERP add-on's setup wizard reads its organisation's setup progress over the company connection.
  • GET /onboarding/partner names the partner your organisation is on, with the partner's number in Uniconta, or partner: null. An application install reads it on onboarding:read.
  • POST /onboarding/partner/uniconta-pid-proposals lets an application install holding the new partner:propose scope propose the Uniconta number of its organisation's partner. It sets nothing: we check the number first, and GET /onboarding/partner shows it once confirmed.
  • POST /demo/reset resets a demo organisation's demo data and sends it fresh opening statements, for an application install holding payments:submit. organisation.demo on GET /oauth/userinfo says whether the organisation is one; it never is in production.
  • GET /journal/documents without limit returns up to 1000 documents a page with a nextCursor to follow, and a malformed cursor is a 400 invalid_cursor with or without limit.
  • GET /journal/documents/{id}/export.camt053 returns a bank statement as camt.053.001.02 for an ERP's own statement import, whatever format the bank sent and after its original file has expired.
  • GET /journal/audit/verify answers an application install holding payments:read, for its own organisation. A broken chain is still a 200 with ok: false, so read ok; the call re-reads the whole log, so make it when someone asks, not on a timer.
  • POST /connections/{id}/intraday-report fetches the bank's intraday account report (camt.052) now. Danske Bank generates the report on each request, so it arrives only when you ask; each report becomes a journal document. A connection asks its bank at most once per 15 minutes: a request inside them gets the last report again, with reused: true and its fetchedAt.
  • Changed: the SpareBank 1 and Eika savings banks are each listed under their own key and name (for example jaeren-sparebank-no), and eika-alliance-no and sparebank-1-alliance-no are removed. Connect to the customer's own bank. Its setup takes the bank ID from the bank: GET /banks/{bankKey}/setup-info gives it as knownServer.bankId.
  • Changed: POST /connections/{id}/activate refuses a DNB Norway (dnb-no) connection until its customer number, division number and the certificate that signs its payment files are on it, and asks it for no PGP keys. PUT /connections/{id}/asic-signing-material answers 400 validation_failed for a certificate and key that could not sign a payment file: either unreadable, a passphrase-protected or non-RSA key, or a key that is not the certificate's. The SFTP setup wizard's step types can read asic-signing-cert.
  • Changed: POST /connections/{id}/activate refuses a SpareBank 1 or Eika connection, which runs through TietoEvry, until its PGP key pair is generated, TietoEvry's PGP public key is added and the SFTP password TietoEvry issued is stored. Each upload on such a connection was already refused without TietoEvry's key. Setup for these banks asks for all three (GET /banks/{bankKey}/setup-info).
  • At SpareBank 1 (sr-bank-no, sparebank-1-alliance-no) a company's noApproverCodesEnabled starts true on its first settings save, as at Nordea Norge, Danske Bank Norge and DNB. If the company approves its payments in the bank's netbank, send noApproverCodesEnabled: false on that first save. Companies already connected to these banks keep the answer they had.
  • Changed: POST /connections/{id}/ebics-fetch-hpb answers 400 validation_failed once the EBICS connection has gone live, before it contacts the bank. Fetch a live connection's bank keys with POST /connections/{id}/ebics-refetch-hpb, which shows changed fingerprints and stores new keys only when you accept them. POST /connections/{id}/activate refuses a deactivated EBICS connection the same way.
  • POST /connections/{id}/ebics-fetch-hpb answers 409 ebics_bank_keys_untrusted when the bank's EBICS keys are refused: a published fingerprint that does not match, a key-management mode the bank publishes no fingerprints for, or an answer naming another Host ID. details.reason says which, and the message says what to do next. These answered 500 before; nothing is saved either way.
  • POST /connections/{id}/ebics-fingerprints-confirmed answers 409 ebics_bank_keys_untrusted (details.reason: fingerprint_mismatch) when the connection's EBICS bank keys differ from a fingerprint the bank publishes. It recorded a confirmation before; now nothing is recorded, and the next step is to fetch the bank's keys again.

September 2026

September added payment states to handle, a banking-day lookup and the bank's payee check for German and Austrian SEPA files.

  • Removed: the platform company-list and admin-roster paths now return 404. GET /me/organisations lists the companies you reach.
  • Removed fields: approverUserIds on connections (read GET /approvals/bank-rosters), payload.rawXml on GET /journal/documents/{id} (fetch GET /journal/documents/{id}/file, see Receiving Results), platformId and subdomain in the sandbox and signup responses, and dashboardLogin.password (open setPasswordUrl instead, see Getting Started). The approver's masked ID is ssnFirst6, not ssnLast4.
  • New states to handle: payment status awaiting-signer-release (at the bank, waiting for your signers), payment status rejected-by-approver (final, never sent), reconciliation state not-sent (the bank never received it) and statement status unknown (an entry code we do not recognise, never treated as settled money). See Receiving Results.
  • Changed: GET /profiles lists each bank's paymentTypes as codes, one-time links carry their token after #, and platformId is optional on every request.
  • Submitting payments: a messageId is one payment, so re-sending one returns the original, or 409 message_id_already_used when that payment was rejected or cancelled; send a corrected payment under a new messageId. From 30 days past due, POST /journal/payments answers 402 billing_past_due, and an unpaid invoice adds a BankConnector-Billing-Warning header to every response.
  • GET /banking-days/adjust returns the banking day on or next to a date. A SEPA file to a German or Austrian EBICS bank goes out with the bank's payee check, set for multi-payment files by GET/PATCH /company/payee-verification.

August 2026

August introduced dated versions and made the journal's names consistent.

  • Removed: /bank-requests is now /bank-coverage-requests, and the old path returns 404.
  • Removed and renamed fields: whole-document responses no longer return slaAlertSentAt or sideEffectsCompleted, and retrieved, retrievedAt, unretrieved and wasAlreadyRetrieved are collected, collectedAt, uncollected and wasAlreadyCollected, with matching filters. The TypeScript SDK's journalList() takes uncollectedOnly.
  • New version 2026-08-25: a payment we cannot process is 422, not 400. See 2026-08-25.