sandbox.bankconnector.com API Reference

Validation Reference

Audience: 🔌 Integration Client

When a payment is rejected 422, the reasons come back as a list of issues in error.details.issues. Each issue looks like:

{
  "code": "PAYMENT_TYPE_SEPA_EUR_ONLY",
  "level": "payment-type",
  "severity": "error",
  "message": "SEPA payments must be in EUR.",
  "path": "payments[0].transactions[0].currency"
}
  • severity is error (blocks) or warning (informational: the payment still proceeds). It's omitted when it would be error (the default); check valid / the absence of error-severity issues to know if a payment can proceed.
  • level tells you which stage raised it: generic, bank, or payment-type.

Validation runs in four levels, and stops at the first structural failure:

  1. Structural (schema): if the JSON doesn't match the canonical schema, you get only GENERIC_SCHEMA_* issues and nothing else runs. Both a malformed body and a schema-invalid canonical payment return 400 here; a well-formed payment that fails a semantic rule (levels 2–4 below) returns 422.
  2. Generic rules: currency, amount, dates, references (below).
  3. Bank rules: per-bank requirements (BIC required, national account checksums, etc.).
  4. Payment-type rules: per scheme (SEPA EUR-only, KID checksum, etc.).

Generic rules (apply to every payment)

CodeSeverityMeaning
GENERIC_SCHEMA_*errorJSON failed the canonical schema (per-field codes, e.g. GENERIC_SCHEMA_AMOUNT_REQUIRED)
GENERIC_AMOUNT_POSITIVEerroramount ≤ 0 (so "0.00" fails here)
GENERIC_AMOUNT_PRECISIONerrora non-zero sub-unit the currency can't represent (e.g. "1000.50" JPY). Trailing zeros ("1000.00") are fine; the engine converts precision for you
GENERIC_CURRENCY_CODE_INVALIDerrornot an active ISO 4217 code
GENERIC_COUNTRY_CODE_INVALIDerrordebtor country not ISO 3166
GENERIC_PAYMENT_ID_DUPLICATEerrorpaymentId reused across payments
GENERIC_END_TO_END_ID_DUPLICATEerrorendToEndId reused in the batch
GENERIC_INSTRUCTION_ID_DUPLICATEerrorinstructionId reused in the batch. InstrId is a point-to-point de-duplication reference, so a repeat can silently drop a real payment
GENERIC_CREDITOR_REFERENCE_RF_INVALIDerrorRF reference fails ISO 11649 mod-97
GENERIC_CREDITOR_REFERENCE_KID_INVALIDerrorKID fails MOD10/MOD11
GENERIC_PURPOSE_CODE_NOT_PLACEABLEerrora purposeCode override longer than the ISO Purp/Cd limit (4 chars) with no corridor rule to place it. Use a 4-char ISO code, or supply regulatoryReporting explicitly
GENERIC_BIC_IBAN_COUNTRY_MISMATCHwarningBIC country ≠ IBAN country
GENERIC_EXECUTION_DATE_PASTwarningdate is in the past
GENERIC_EXECUTION_DATE_FAR_FUTUREwarningmore than a year ahead
GENERIC_EXECUTION_DATE_NON_BANKINGwarningweekend/holiday; suggests next banking day
GENERIC_EXECUTION_DATE_INTL_TOO_SOONwarninga cross-border payment to a country whose banking calendar isn't modelled, with little lead time. International settlement usually needs a few business days
GENERIC_MIXED_CURRENCY_BATCHwarningmore than one currency in one payment

The duplicate-id errors matter: banks de-duplicate on paymentId, endToEndId and instructionId and may silently drop repeats, so BankConnector blocks them up front. Keep them unique within a batch.

Profile warnings (character set / truncation)

CodeSeverityMeaning
PROFILE_NAME_TRUNCATEDwarninga name was shortened to the bank's limit
PROFILE_REMITTANCE_TRUNCATEDwarningremittance text was shortened
PROFILE_CHARSET_TRANSLITERATEDwarningcharacters were transliterated to the SEPA set
PROFILE_NAME_CHARS_DROPPEDwarninga mixed-script name survives transliteration but loses characters that have no Latin rendering ("王伟 Ltd""Ltd"). The bank receives a materially different name (Confirmation-of-Payee mismatch risk). Supply the Latin form of the full name
PROFILE_ID_CHARSET_INVALIDwarningan identifier (messageId, paymentId, instructionId, endToEndId) has characters outside the SEPA set. Unlike names, ids are sent verbatim (to keep pain.002/camt correlation), so the bank may reject the batch or transcode the id and break correlation. Use only A–Z a–z 0–9 and / - ? : ( ) . , ' + and space
PROFILE_NAME_UNREPRESENTABLEerrora required name transliterates to empty (e.g. CJK-only)

Remember the order: transliterate, then truncate. Transliteration can _expand_ text (äae, ßss), so a name that looks short can still truncate.

Note the split between the two "lossy name" codes: PROFILE_CHARSET_TRANSLITERATED fires on any change (ée counts); PROFILE_NAME_CHARS_DROPPED fires only when characters are removed outright, which is the one that actually changes who the bank thinks is being paid.

Bank-rule examples

Different banks require different things. Common ones:

  • BANK_BIC_REQUIRED: this bank requires the debtor BIC.
  • BANK_BIC_VALUE_INVALID: the debtor BIC doesn't match the expected institution.
  • BANK_INITIATING_PARTY_ID_REQUIRED: an initiatingParty.organisationId is required.
  • BANK_DEBTOR_COUNTRY_REQUIRED / BANK_DEBTOR_ACCOUNT_CURRENCY_REQUIRED.
  • BANK_BBAN_FORMAT_INVALID / BANK_BBAN_CHECKSUM_INVALID: national account number checks (Norwegian MOD-11, Danish structure, Polish NRB mod-97).

Query GET /banks/<bank>/payment-types and the bank's setup info to see what a given bank expects before you build against it.

Payment-type rules (by scheme)

TypeKey requirements
sepacreditor IBAN required; EUR only
sepa-instantas SEPA + amount ≤ €100,000
internationalcreditor account and creditor BIC required
fik (DK)valid FIK reference (kortart 71 + Luhn)
kid (NO)2–25 digits, MOD10/MOD11
bankgiro / plusgiro (SE)Luhn-checked, digit-length bounded
bacs / chaps / faster-payment (UK)6-digit sort code + 8-digit account; GBP only; Faster ≤ £1,000,000
ach / wire (US)9-digit routing + ABA checksum; USD only
ch-domestic / ch-qr (CH)creditor IBAN + CHF/EUR; QR adds a 27-digit reference check
eft (CA)routing + account; CAD only

If you omit paymentType, the engine attempts to infer it and returns autoSelected in the response. If it can't (PAYMENT_TYPE_AUTO_UNRESOLVED) or the type isn't offered by that bank (PAYMENT_TYPE_NOT_OFFERED), you get an error; set it explicitly when in doubt.

Handling issues in your client

if (res.status === 422) {
  const { error } = await res.json();
  const blocking = (error.details?.issues ?? []).filter((i: any) => i.severity !== "warning");
  const warnings = (error.details?.issues ?? []).filter((i: any) => i.severity === "warning");
  // Surface `blocking` to whoever owns the payment data; log `warnings`.
}

Treat errors as "fix and resubmit," and warnings as "worth logging, but it went through." Don't discard warnings: a truncation or non-banking-day warning is often the first sign of a data-quality issue upstream.