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

← All documentation

Getting started

The PaySaxas XS2A API implements the Berlin Group NextGenPSD2 XS2A Implementation Guidelines v1.3.15 (JSON). This page takes you from a certificate to a first account read.

Who can connect

Any firm authorised under PSD2 as an AISP, PISP or CBPII in an EEA member state, holding a valid eIDAS QWAC that carries its PSD2 role attributes.

There is nothing to apply for. No contract, no fee, no onboarding form, no “contact us first” step — RTS 2018/389 Art. 32(3) forbids all of them. Your first request with a valid certificate registers you automatically.

We do corroborate your authorisation against the EBA/NCA register when you first appear and periodically after that. If your home regulator withdraws your authorisation, your access stops — that is the register speaking, not us.

Base URLs

Environment Host
Sandbox — start here https://xs2a.paysaxas.money
Production https://xs2a.paysaxas.com

The sandbox is also listed first in the OpenAPI servers block, so a generated client points at it by default rather than at production.

Both terminate mutual TLS. A request without a client certificate is refused with CERTIFICATE_MISSING, whatever else it contains.

Start in the sandbox — see sandbox.md.

Headers you will always send

Header Required Meaning
X-Request-ID Yes UUID. Echoed back. The idempotency key for payment initiation.
Consent-ID On account data and CoF The consent you are acting under
PSU-IP-Address When the customer is present Marks the request attended — see below
TPP-Redirect-URI Yes, on consent and payment creation Where we return the customer. Absolute https, no fragment — anything else is a FORMAT_ERROR
TPP-NOK-Redirect-URI Optional Where we return them if they decline

Attended vs unattended

PSU-IP-Address is not bookkeeping. Sending it says “the customer is at their screen right now”, and those requests are uncapped. Requests without it are unattended and limited to four per consent, per data type, per day (RTS Art. 36(5)). Send it when it is true, and do not send it when it is not.

Three requests, end to end

curl -X POST https://xs2a.paysaxas.com/v1/consents \
  --cert tpp-qwac.pem --key tpp-qwac.key \
  -H "Content-Type: application/json" \
  -H "X-Request-ID: 99391c7e-ad88-49ec-a2ad-99ddcb1f7721" \
  -H "TPP-Redirect-URI: https://your-app.example/psd2/return" \
  -H "PSU-IP-Address: 203.0.113.9" \
  -d '{
        "access": {
          "accounts":     [{"iban": "FI2112345600000785"}],
          "balances":     [{"iban": "FI2112345600000785"}],
          "transactions": [{"iban": "FI2112345600000785"}]
        },
        "recurringIndicator": true,
        "validUntil": "2026-12-31",
        "frequencyPerDay": 4
      }'
{
  "consentId": "PSXS-CONSENT-3f2b...",
  "consentStatus": "received",
  "_links": {
    "scaRedirect": { "href": "https://platform.paysaxas.com/customer/xs2a/authorise/AUTH-...?state=..." },
    "status":      { "href": "/v1/consents/PSXS-CONSENT-3f2b.../status" }
  }
}

The consent does not exist yet. received means “asked for, not granted”.

Note that we did not tell you whether we hold that IBAN. At this point nobody has authenticated, so answering would disclose facts about accounts no consent covers. A well-formed IBAN we do not hold is accepted exactly like one we do.

2. Send the customer to scaRedirect

They authenticate on our surface and see exactly what you asked for: your name, the data types, the accounts, the validity. They approve or decline, and we return them to your TPP-Redirect-URI with the state we generated. See what state is and how long it lasts.

You never see their credentials. There is no embedded or decoupled alternative — see sca-redirect-flow.md.

3. Read the accounts

curl https://xs2a.paysaxas.com/v1/accounts \
  --cert tpp-qwac.pem --key tpp-qwac.key \
  -H "X-Request-ID: 6d2b1e64-8a0f-4e0e-9f0c-2b0a2c9b1f11" \
  -H "Consent-ID: PSXS-CONSENT-3f2b..." \
  -H "PSU-IP-Address: 203.0.113.9"
{
  "accounts": [
    {
      "resourceId": "0f0f...",
      "iban": "FI2112345600000785",
      "currency": "EUR",
      "name": "Operating EUR",
      "product": "current_account",
      "cashAccountType": "CACC"
    }
  ]
}

Only the accounts the customer actually holds and consented to appear. If you asked about others, they are silently absent rather than reported.

resourceId is what you address the account by. It is the {account_id} path parameter for everything account-scoped:

GET /v1/accounts/0f0f.../balances
GET /v1/accounts/0f0f.../transactions

It is opaque, stable for the life of the account, and not the IBAN — an IBAN in that position is not recognised. Read it from the listing rather than constructing or storing a path.

What to build next

Things that commonly surprise integrators

  1. A 4xx is often the interface working. A refused consent is an answer, not an outage. Our published availability figures count 5xx only, and you should treat the two differently as well.
  2. Some refusals are identical on purpose. An unknown consent and another provider’s consent return byte-identical responses. Do not build logic that depends on telling them apart — there is nothing to tell.
  3. Consents expire on a clock you can see. 180 days maximum from the customer’s authentication. Read validUntil on the consent and renew before it, rather than waiting for CONSENT_EXPIRED.
  4. Transaction history is paged — follow _links.next. A response carries at most 250 rows. When more history exists, transactions._links.next.href is present and already carries your bookingStatus and date filters; follow it until it is absent, which is the end. It is omitted rather than emptied, so there is nothing to poll. If you would rather build URLs yourself, every transaction carries an entryReference you can pass back as entryReferenceFrom. Paging is keyset-based: a payment arriving mid-traversal cannot shift a page boundary, so you will not see a duplicate or miss a row.
  5. booked and pending are different money. On GET /v1/accounts/{id}/transactions, bookingStatus selects which arrays come back — booked (the default), pending, or both. A pending transaction is authorised but not yet booked and can still fail; do not reconcile against it. A payment the customer cancelled, or that we refused, appears in neither array: no money moved, so there is nothing on the account to report.