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
codenotmessage) 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, notvalidation_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, not400, on every route, with each problem on its field inerror.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, not400.400means we could not read your request at all (not JSON, a bad header);422means the request was fine and the payment inside it is not something we can accept, with the reasons inerror.details.issues. See 2026-08-25 below, and Errors & Retries. - Canonical input schema
1.1(additive over1.0: structuredpostalAddress,regulatoryReporting, andpurposeCode/localInstrument/categoryPurposeoverrides; 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/listandGET /journal/documents) each item carriescollected/collectedAt, the/journal/listenvelope carries anuncollectedcount, and the filters are?uncollectedOnly=trueand?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.
| Refusal | 2026-06-01, 2026-08-25 | 2026-10-20 | 2026-10-21 |
|---|---|---|---|
| the connection is not ready for the request | 400 validation_failed | 422 validation_failed | 409 invalid_state |
| a value cannot be used: missing or malformed in the request, or stored but malformed | 400 validation_failed | 422 validation_failed | 422 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-20and you keep422.
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.
| Request | 2026-06-01, 2026-08-25 | 2026-10-20 |
|---|---|---|
| a body that fails the route's schema | 400 | 422 |
| a body with a field the route does not read | accepted, the field dropped | 422, the field named |
| a body that is not JSON | 400 | 400 (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
422wherever you handle a refused write. - Nothing forces you to move. Keep sending
2026-08-25and you keep the400and 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.
| Request | 2026-06-01 | 2026-08-25 |
|---|---|---|
POST /journal/payments, a transaction with no creditor account | 400 | 422 |
POST /journal/payments/preview, same body | 400 | 422 |
POST /convert/{bank}, body null, 42, [] or {"payments":[null]} | 400 | 422 |
POST /convert-async/{bank}, same bodies | 400 | 422 |
POST /validate/{bank}, same bodies | 400 | 200, valid: false |
| a payment that parses but breaks a rule | 422 | 422 (unchanged) |
| a body that is not JSON at all | 400 | 400 (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
422wherever you handle a rejected payment, and read400as "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-
200fromPOST /validate/{bank}as "could not validate", readvalidinstead. 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-01and you keep the400answers.
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
httpsredirect 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 toPOST /oauth/tokenandPOST /oauth/revokeby HTTP Basic or asclient_secret(a missing or wrong one is401 invalid_client). See From a web or mobile app. - Added: each row of
GET /journal/listandGET /journal/documentscarriesenvironment("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"onPOST /webhooksposts each event into a Microsoft Teams chat or channel as a card. A webhook'stransportcan now read"teams". - Changed: a
"*"webhook subscription (or one created withouteventTypes) receives the events in the event catalog and no others. To keep receiving an event the catalog does not list, name it ineventTypes. - Changed: a request to a route that needs a credential that states
platformId(in the query string or the JSON body) answers400 invalid_parameter, naming the parameter, on every version. A public route refuses it like any other unknown body field. Name the organisation withcompanyIdalone, and send noplatformId, 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-hpband the other steps that call the bank) answers502 bank_unavailablewitherror.details.nextSteps, including a host name that does not exist; it answered500 internal_error. - Changed:
POST /approver-identityrefuses a fødselsnummer that is not valid, or a District user ID that is empty or over 35 characters, asvalidation_failed(422on2026-10-20,400before it), as a refused request body is. It answered422on every version. - Changed:
POST /company/require-2farefuses a body withoutenabledasvalidation_failed(422on2026-10-20,400before it). It read a missingenabledasfalseand turned the requirement off. Send"enabled": falseto turn it off. - Changed:
POST /connections/{id}/approversanswers an approval-governance refusal with its own code (409 policy_change_in_flight,422 policy_quorum_shortfall,400 policy_roster_invalid), asPOST /approvals/bank-rostersdoes; it answered500 internal_error. - New:
GET /banks/{bankKey}/sftp-naminganswers 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 answers403 bank_guide_access_required, asPOST /connectionsdoes. - New:
GET /profiles/memberslists the Sparkassen and cooperative banks (Volksbanken, Raiffeisenbanken) the German group profiles serve, each with its town, BIC and bank codes andbankKey, 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 withmemberBankCodeonPATCH /banks/{bankKey}/settings. - Changed:
PATCH /banks/{bankKey}/settingsrefuses amemberBankCodethat would change the member bank of a group profile the company has a connection on, or a setup in progress for another member, with409 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) is409 invalid_state; a value that cannot be used stays422 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 is422, and a field the route does not read is refused, naming the field. On every version, a refused body lists each problem inerror.details.issuesas{ 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 /usersanswers409 conflictfor an email address already registered (it was400 bad_request). A connection route naming an unknown or deleted connection answers404 connection_not_found, where some answered400 validation_failedor404 not_found.GET /connections/{id}/ebics-letterbefore the subscriber keys exist answers400 validation_failed(it was404). On2026-10-20a connection'svalidation_failedrefusal is422, 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/authorizelists the second half asidentityScopes, and the loopback carriesidentity_codebesidecodewhen the person may approve there, orerror_reasonwhen they may not. Each code redeems atPOST /oauth/tokenwith the samecode_verifier. - Changed:
POST /signup'serpPartnerlinks a partner only when the general signup lists it (the partnerGET /signup/erp-partner/uniconta/{pid}names); any other number links none, as a signup with no partner.partnerAccessbeside a number that links none is refused422 validation_failed(details.field: partnerAccess), where it was required and ignored. SendpartnerAccessonly for a partner that route names. - New: an ERP add-on can open the signup page instead of sending
POST /signupfrom 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 newGET /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-apiorerp-addon(with its ERP), chosen at signup (product,productErponPOST /signup) and read atGET /company/product. An Admin changes it withPATCH /company/productuntil the organisation chooses a package; after that the route answers409 package_chosen, and on an Embedded organisation409 product_embedded. - Changed:
POST /connections/{id}/ebics-paramsanswers400 validation_failedto a newsignatureVersiononce 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-inforefuses an SFTP host-key pin that is not a SHA-256 fingerprint (hostFingerprint,downloadHostFingerprint), as422 validation_failedon2026-10-20and400before it, and saves nothing. It saved an MD5 or half-typed pin before, and every connection to the bank then failed. SendSHA256:plus base64 or 64 hex characters; either is stored asSHA256:plus unpadded base64. - Changed:
GET /webhooks/{id}/deliveriesandPOST /webhooks/{id}/redeliver-failedhand out a differentnextCursor(still opaque), refuse a malformedcursorwith400 invalid_cursor, and end withnextCursor: nullon the last page, even when it is exactly full. A cursor issued before this change is malformed, and a cursor built from a delivery'sidis refused: passnextCursorback as it came.redeliver-failedrefuses asincethat is not an ISO-8601 date or time, or not a real one (2026-02-30), with400 invalid_since. - Changed:
GET /accounts,GET /company/api-keys,GET /installs,GET /me/organisations,GET /partner/customer-logs,GET /approvals/pendingandGET /approvals/oversighthand out a differentnextCursor(still opaque), and refuse a malformedcursorwith400 invalid_cursor. A cursor issued before this change is malformed, and a cursor built from an item'sidis refused: passnextCursorback as it came. The two approval lists end withnextCursor: nullon the last page, even when it holds exactlylimitrequests. - Renamed: four remittance warnings carry one code each, whatever bank or standard raises them, on every version:
BANK_REMITTANCE_DOCUMENTS_AS_TEXT(was alsoBANK_SE_,BANK_SEPA_,BANK_NPC_andBANK_NL_REMITTANCE_DOCUMENTS_AS_TEXT),BANK_REMITTANCE_ADDITIONAL_INFO_NOT_SENT(was alsoBANK_SE_,BANK_SEPA_andBANK_NPC_REMITTANCE_ADDITIONAL_INFO_NOT_SENT, andBANK_SEPA_REMITTANCE_TEXT_NOT_SENTonadditionalInfo),BANK_REMITTANCE_TEXT_NOT_SENT(wasBANK_SE_andBANK_NPC_REMITTANCE_TEXT_NOT_SENT, andBANK_SEPA_REMITTANCE_TEXT_NOT_SENTon the text or documents) andBANK_REMITTANCE_TEXT_JOINED(was alsoBANK_SE_andBANK_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,endToEndIdorinstructionIdmade 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./convertanswered200with a blank id element before. Spaces and invisible characters inside or around other characters are kept as sent. - Changed: a
messageId,paymentId,endToEndIdorinstructionIdmade 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./convertanswered200with an empty id element before, andPOST /journal/paymentsrefused it later asXSD_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,directionorstatusoutside its set) or alimitthat is not a number answers400 invalid_parameter, naming the parameter, instead of an empty or unfiltered page.GET /approvals/pendingandGET /approvals/oversightpage withcursor, as every other list does;afterstill works. To poll for documents useGET /journal/list?uncollectedOnly=true. - New status: the certificate renewal routes (
POST /connections/{id}/renew-danskeand the five others) answer409 renewal_in_progresswhile 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) andPOST /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-crosskeyand/renew-bankconnect. One path does both acts for every bank:POST /connections/{id}/ws-certificatewithaction(activateorrenew) andfamily, thekindthatGET /banks/{bankKey}/setup-infoserves for the bank. The fields and the errors are the same, except that an unknown connection now answers404 connection_not_foundand afamilythat is not the connection's own is refused with400 validation_failed. Only a signed-in Admin calls it, as before. - Changed:
GET /accounts/statementsanswers one page of the list envelope:itemsreplacesstatements, andnextCursor(pass it back as?cursor=, with?limit=up to 500) points at the next older page.accountandgapsstay; each page'sgapsare the ones whose later statement is on that page. FollownextCursoruntil it isnullto read the whole account. - Removed:
POST /partner/firm/members/{userId}/removenow returns404. A partner firm's administrator removes a person withDELETE /partner/firm/members/{userId}, which takes no body and answers as before. - Changed: a list's last page carries
nextCursor: nulleven when it holds exactlylimititems, onGET /journal/documents,/journal/list,/journal/events(paged bycursor) and/journal/reconciliation.GET /bank-coverage-requestsrefuses a malformedcursorwith400 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 inBankConnector-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 withPATCH /company/name, andGET /company/legal-identityneeds a signed-in Admin (an API key gets403). - Removed field:
guide.fieldCopyandbankConnect.fieldCopyon theGET /banks/{bankKey}/setup-inforesponse. Each entry offields[]carries the same label, placeholder and help, in the order the enrolment form asks for them. - Removed field:
approval.limitEquivalenceon thePOST /journal/paymentsresponse. The response'sapprovalis the request asGET /approvals/pendingshows it. - Removed field:
grantsRevokedon theDELETE /connections/{id}response, thegrantedvalue ofreachableBanks[].accessonGET /dashboard, and the webhook event typeconnection.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 oncompanyId, which names your organisation. On a route that needs a credential, a request that statesplatformIdis refused400 invalid_parameter(see the first October entry). - Renamed:
POST /connections/{id}/platform-bank-keyisPOST /connections/{id}/registry-bank-key(the old path returns404), a connection'susePlatformBankKeyisuseRegistryBankKey, andplatformBankKeyonGET /connections/{id}isregistryBankKey. 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,predecessorIdandsupersededByIdare gone from the policy object andstatusisdraftoractive.POST /approvals/policies/{id}/proposeandPOST /approvals/changes/{changeId}/submitreturn404, a pending policy change is the kindpolicy-editwith its proposed rule inproposed, and the error codepolicy_supersededis retired. - Renamed: 36 validation issue codes take their level's prefix,
BANK_on a bank issue andPAYMENT_TYPE_on a payment-type issue, on every version. Each keeps its old spelling after the prefix (NEMKONTO_DKK_ONLYbecomesBANK_NEMKONTO_DKK_ONLY,ACH_ROUTING_REQUIREDbecomesPAYMENT_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 withBANK_and every payment-type issue code withPAYMENT_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/eventsreturns only the events for its own payments and the organisation-wide approval and channel changes, on every version. - An application install holding
statements:readalso reads the events about the organisation's inbound statements onGET /journal/events:file.received, now with the file'stypeand maskedaccountIban, and the statement alerts. Poll forfile.receivedto 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 a404. - An ERP add-on that sends
erp_company_idat 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. Senderp_company_idat every pairing.
- New field to handle:
reissuedBy: "other_sender"onrefusals.resends[]ofGET /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 nodocumentId,journalNoorresendEndToEndId.stateandresolvedstill 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_senderinentryRecon[].stateonGET /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 /signupandPOST /partners/registeraccept an address the other already uses, and the person confirms with the password they have.GET /billing/subscriptionsays why an organisation is on the Free plan infreePlanSource. - New status to handle:
POST /signupneeds the company register to find the company. A number the register does not know, or a company it lists as inactive, is refused with422onregistrationNumber; a register that gives no answer is refused with503 service_unavailablenaming 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'sregisterAnswerback with the signup. - New value to handle:
accessReason: disconnectedonGET /installs. Revoking an install's refresh token atPOST /oauth/revokeends the install, and it is listed asrevokedwith 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_idonGET /oauth/authorize. Every install paired for that company shares onemessageIdcheck, so two companies of one organisation may use the samemessageId, 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_statementonPOST /journal/ingestandPOST /inbound/statement, anderror.details.parseIssuesnames each field. Nothing from the file is stored. A file of this kind fetched from the bank sends nostatement.imported; we keep it and deliver it once we can read it. - Required field:
POST /partners/registertakespartnerType(uniconta-partner,api-partnerorsoftware-vendor) and refuses an application without it with422. A body carryingfirmKind,firmKindOther,customerCount,erpSystemsorerpSystemsOtheris refused with422naming that field. - New status to handle: a partner access request on
GET /partner-access/requestscan readwithdrawn. 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
unconfirmedappears 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. Onlynot-foundmeans you may send it again, under a newmessageId.- Send
resendOfon a re-issued payment to link it to the refused one; the original batch'sGET /journal/documents/{id}/paymentsthen reportsrefusals. POST /journal/payee-verificationschecks payees with a verification-only file, and the results arrive as afile.receivedevent.- New value to handle: a company register lookup's
company.inactiveReasonscan readinsolvency(in receivership, administration, a voluntary arrangement or insolvency proceedings). A UK company number is looked up in Companies House onPOST /signup/register-lookupandPOST /partners/register/lookup. GET /banks/livelists the banks your organisation has an active connection to, per environment, on thebanks:readscope an application install holds.GET /onboarding/statusanswers an application install holding the newonboarding:readscope, so an ERP add-on's setup wizard reads its organisation's setup progress over the company connection.GET /onboarding/partnernames the partner your organisation is on, with the partner's number in Uniconta, orpartner: null. An application install reads it ononboarding:read.POST /onboarding/partner/uniconta-pid-proposalslets an application install holding the newpartner:proposescope propose the Uniconta number of its organisation's partner. It sets nothing: we check the number first, andGET /onboarding/partnershows it once confirmed.POST /demo/resetresets a demo organisation's demo data and sends it fresh opening statements, for an application install holdingpayments:submit.organisation.demoonGET /oauth/userinfosays whether the organisation is one; it never is in production.GET /journal/documentswithoutlimitreturns up to 1000 documents a page with anextCursorto follow, and a malformedcursoris a400 invalid_cursorwith or withoutlimit.GET /journal/documents/{id}/export.camt053returns a bank statement ascamt.053.001.02for an ERP's own statement import, whatever format the bank sent and after its original file has expired.GET /journal/audit/verifyanswers an application install holdingpayments:read, for its own organisation. A broken chain is still a200withok: false, so readok; the call re-reads the whole log, so make it when someone asks, not on a timer.POST /connections/{id}/intraday-reportfetches 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, withreused: trueand itsfetchedAt.- Changed: the SpareBank 1 and Eika savings banks are each listed under their own key and name (for example
jaeren-sparebank-no), andeika-alliance-noandsparebank-1-alliance-noare removed. Connect to the customer's own bank. Its setup takes the bank ID from the bank:GET /banks/{bankKey}/setup-infogives it asknownServer.bankId. - Changed:
POST /connections/{id}/activaterefuses 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-materialanswers400 validation_failedfor 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 readasic-signing-cert. - Changed:
POST /connections/{id}/activaterefuses 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'snoApproverCodesEnabledstartstrueon its first settings save, as at Nordea Norge, Danske Bank Norge and DNB. If the company approves its payments in the bank's netbank, sendnoApproverCodesEnabled: falseon that first save. Companies already connected to these banks keep the answer they had. - Changed:
POST /connections/{id}/ebics-fetch-hpbanswers400 validation_failedonce the EBICS connection has gone live, before it contacts the bank. Fetch a live connection's bank keys withPOST /connections/{id}/ebics-refetch-hpb, which shows changed fingerprints and stores new keys only when you accept them.POST /connections/{id}/activaterefuses a deactivated EBICS connection the same way. POST /connections/{id}/ebics-fetch-hpbanswers409 ebics_bank_keys_untrustedwhen 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.reasonsays which, and the message says what to do next. These answered500before; nothing is saved either way.POST /connections/{id}/ebics-fingerprints-confirmedanswers409 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/organisationslists the companies you reach. - Removed fields:
approverUserIdson connections (readGET /approvals/bank-rosters),payload.rawXmlonGET /journal/documents/{id}(fetchGET /journal/documents/{id}/file, see Receiving Results),platformIdandsubdomainin the sandbox and signup responses, anddashboardLogin.password(opensetPasswordUrlinstead, see Getting Started). The approver's masked ID isssnFirst6, notssnLast4. - New states to handle: payment status
awaiting-signer-release(at the bank, waiting for your signers), payment statusrejected-by-approver(final, never sent), reconciliation statenot-sent(the bank never received it) and statement statusunknown(an entry code we do not recognise, never treated as settled money). See Receiving Results. - Changed:
GET /profileslists each bank'spaymentTypesas codes, one-time links carry their token after#, andplatformIdis optional on every request. - Submitting payments: a
messageIdis one payment, so re-sending one returns the original, or409 message_id_already_usedwhen that payment was rejected or cancelled; send a corrected payment under a newmessageId. From 30 days past due,POST /journal/paymentsanswers402 billing_past_due, and an unpaid invoice adds aBankConnector-Billing-Warningheader to every response. GET /banking-days/adjustreturns 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 byGET/PATCH /company/payee-verification.
August 2026
August introduced dated versions and made the journal's names consistent.
- Removed:
/bank-requestsis now/bank-coverage-requests, and the old path returns404. - Removed and renamed fields: whole-document responses no longer return
slaAlertSentAtorsideEffectsCompleted, andretrieved,retrievedAt,unretrievedandwasAlreadyRetrievedarecollected,collectedAt,uncollectedandwasAlreadyCollected, with matching filters. The TypeScript SDK'sjournalList()takesuncollectedOnly. - New version
2026-08-25: a payment we cannot process is422, not400. See 2026-08-25.