PaySaxas XS2A
PSD2 access to account — Berlin Group NextGenPSD2 v1.3.15

← All documentation

Error catalogue

Every error uses the Berlin Group tppMessages envelope:

{
  "tppMessages": [
    { "category": "ERROR", "code": "CONSENT_EXPIRED", "text": "The consent has expired." }
  ]
}

code is stable and safe to branch on. text is a short sentence for a human and may change without notice — do not parse it.

path — which field was wrong

When a refusal is about a specific field in your request body, we name it:

{
  "tppMessages": [
    {
      "category": "ERROR",
      "code": "FORMAT_ERROR",
      "path": "creditorAccount.iban",
      "text": "creditorAccount.iban is not a valid IBAN."
    }
  ]
}

Dot-notated, matching the request body. code tells you what kind of problem it is; path tells you where. Between them a FORMAT_ERROR is actionable — you can highlight the offending field for your own user — without reading text, which you are told not to parse.

path is absent when no single field is at fault, and that absence is meaningful rather than an omission. A refusal that names no field is one you cannot fix by correcting a value: a compliance outcome, a header problem, a state conflict, or a PAYMENT_FAILED whose cause is deliberately not disclosed. Treat “no path” as “do not retry a variation of this request”.

Codes we emit

HTTP Code When What to do
400 FORMAT_ERROR A malformed request: missing X-Request-ID, a bad IBAN, an unparseable amount or date Fix the request. The text says what is wrong; this class of error is safe to surface to your own logs.
400 PRODUCT_INVALID A currency other than EUR We are EUR-only. Do not retry.
400 PRODUCT_UNKNOWN A payment product we do not offer Use sepa-credit-transfers or instant-sepa-credit-transfers.
400 SERVICE_INVALID A bank-offered global access flag (availableAccounts, allPsd2) Request dedicated accounts by IBAN instead.
400 PAYMENT_FAILED Initiation refused See below — the cause is deliberately not disclosed.
401 CERTIFICATE_MISSING No client certificate reached us Check your mTLS configuration and that you are on the XS2A host.
401 CERTIFICATE_INVALID The certificate could not be parsed or validated Check the chain and that the QWAC is intact.
401 CERTIFICATE_EXPIRED Outside its validity window Rotate. Do not wait for expiry — see certificates.
401 CONSENT_EXPIRED Past validUntil Create a new consent and re-authenticate the customer.
401 CONSENT_INVALID The consent does not cover this request: wrong scope, wrong account, or no longer active Re-read the consent. If it was revoked, ask the customer for a new one.
403 CONSENT_UNKNOWN No consent addressable under that identifier for you Do not retry with variations — see below.
403 ROLE_INVALID Your certificate lacks the PSD2 role this service needs Nothing to retry; the role is in the certificate.
403 SERVICE_BLOCKED Your registration is not currently permitted See certificates.
404 RESOURCE_UNKNOWN No such payment or authorisation for you Check the identifier. Consents never 404 — see below.
409 REQUEST_ID_REUSED You reused an X-Request-ID with a different request body Generate a fresh UUID. We refuse rather than returning the first result, so a changed instruction is never mistaken for a completed one. Remembered for 48 hours.
405 CANCELLATION_INVALID The payment can no longer be cancelled The funds have moved. Nothing to retry.
429 ACCESS_EXCEEDED The four-per-day unattended cap is reached Wait for the daily reset, or make the request attended by sending PSU-IP-Address when the customer is genuinely present.

Refusals that are identical on purpose

Three pairs are byte-identical, and no amount of retrying will separate them:

  1. An unknown consent id and another provider’s consent — both CONSENT_UNKNOWN.
  2. An account outside the consent and an account belonging to someone else — both CONSENT_INVALID.
  3. Every payment-initiation failure — insufficient funds, unknown debtor account, a creditor IBAN we cannot route to, a creditor account belonging to the debtor’s own customer, limit breach, compliance hold — all PAYMENT_FAILED with the same sentence.

Each pair would otherwise be an oracle: ask a question, learn a fact about a customer that no consent covers. Treat them as one condition.

Consents never return 404

403 CONSENT_UNKNOWN subsumes “not found” on every consent operation. An id that never existed and one belonging to another provider answer identically — which is the whole point, since a distinguishable 404 would let you enumerate which consent ids are real.

So the consent operations declare no 404 at all, and a 404 branch on them is dead code. The only 404 in this interface is RESOURCE_UNKNOWN on a payment or on the templated authorisation read, where the id is one you were given rather than one you could guess.

Distinguishing our failures from your refusals

A 4xx means the interface understood you and said no. A 5xx means we broke. Only 5xx counts against our published availability figures, and your alerting should draw the same line — a rise in CONSENT_EXPIRED is a renewal problem in your integration, not an outage in ours.

Code Status Meaning
INTERNAL_SERVER_ERROR 500 We failed. Says nothing about your request — replay it unchanged after a backoff.

Do not require a 5xx to parse. The application emits the usual tppMessages envelope, but a failure above it — load balancer, TLS termination, a timeout that never reaches the application — produces whatever that layer emits, which may be HTML or empty. 502, 503 and 504 mean the same thing as 500 and are handled the same way. Branch on the status code.

Retry guidance

Situation Retry?
5xx or a connection failure Yes, with backoff. Payment initiation is idempotent on X-Request-ID.
ACCESS_EXCEEDED Not today, unless the request becomes attended.
Any other 4xx No. Fix the request, the consent, or the certificate.