Payment initiation
Products
| Product | Path segment |
|---|---|
| SEPA credit transfer | sepa-credit-transfers |
| SEPA instant credit transfer | instant-sepa-credit-transfers |
EUR only. Bulk payments, periodic payments and signing baskets are outside the interface because they are outside the customer’s own portal — not because they are unbuilt.
Initiating
curl -X POST https://xs2a.paysaxas.com:8443/v1/payments/sepa-credit-transfers \
--cert tpp-qwac.pem --key tpp-qwac.key \
-H "Content-Type: application/json" \
-H "X-Request-ID: 3c2f0e9a-2b62-4b0e-9b2d-2f0a8a2b1c33" \
-H "TPP-Redirect-URI: https://your-app.example/psd2/return" \
-H "PSU-IP-Address: 203.0.113.9" \
-d '{
"debtorAccount": {"iban": "FI2112345600000785"},
"instructedAmount": {"currency": "EUR", "amount": "125.50"},
"creditorAccount": {"iban": "FI4550009420888888"},
"creditorName": "Beta Oy",
"remittanceInformationUnstructured": "Invoice 2026-114"
}'
{
"transactionStatus": "RCVD",
"paymentId": "8f14...",
"authorisationId": "AUTH-...",
"_links": {
"scaRedirect": { "href": "https://platform.paysaxas.com/customer/xs2a/authorise/AUTH-...?state=..." },
"self": { "href": "/v1/payments/sepa-credit-transfers/8f14..." },
"status": { "href": "/v1/payments/sepa-credit-transfers/8f14.../status" },
"scaStatus": { "href": "/v1/payments/sepa-credit-transfers/8f14.../authorisations/AUTH-..." }
}
}
Then send the customer to scaRedirect. Nothing moves until they authorise. Store
the state on that link against the payment and check it when they come back —
see what state is and how long it lasts.
Field notes
debtorAccountis optional. Omit it and the customer chooses the account to pay from when they authorise, on our screen (Berlin Group IG v1.3.15 §11.1, footnote 11). You get the samepaymentId,authorisationIdandscaRedirect. Until the customer has chosen,GET /v1/payments/{product}/{paymentId}readsRCVDwith nodebtorAccount(RJCTif they decline or it lapses); afterwards it carries the account they chose. A request withoutdebtorAccountis checked the same way at initiation (product, amount, creditor format). Anything that depends on the paying account is decided once the customer has chosen it, and if the payment can’t be made from that account, they are asked to choose another.DELETEbefore they choose cancels it (CANC). Use this when you don’t know your customer’s IBAN: it saves them typing it.creditorAccount.bicis optional. We resolve it where we can. Send it if you have it.creditorAccount.ibanmust be one we can route to. A malformed IBAN is aFORMAT_ERROR. Anything else we cannot route — including a PaySaxas IBAN with no live account behind it — is refused asPAYMENT_FAILEDlike every other initiation failure, so you cannot tell those cases apart, by design. The rule is the same one the account holder’s own banking screen applies; nothing is reachable through this interface that is not reachable there.remittanceInformationUnstructuredis optional on the wire; if you omit it we record a neutral description rather than rejecting the instruction.creditorNameis optional on the wire, but send it. Verification of Payee compares it with the name the payee’s account is held under, and the customer sees the result before approving.instructedAmountmust be EUR and greater than zero.ultimateDebtor(Latin letters, at most 70 characters as sent: accents are dropped andßbecomesss) is accepted and sent to the payee’s bank in that form; a longer name or one in another script is refused withFORMAT_ERRORonultimateDebtor. It is required when the debtor account holds a regulated firm’s client funds: without it the initiation is refused withFORMAT_ERRORonultimateDebtor, before the customer is asked to authorise.requestedExecutionDate,endToEndIdentificationandcreditorAddressare accepted and ignored. Future-dated execution is not offered: a payment is executed once the customer authorises it.
Idempotency
X-Request-ID is the idempotency key. Replaying the same value returns the
first payment rather than initiating a second — safe to retry a timeout.
Use a fresh UUID for a genuinely new payment.
The full contract, so you can reason about your retry logic:
| Question | Answer |
|---|---|
| Same key, same body | Returns the first payment. Retry a timeout freely |
| Same key, different body | 409 REQUEST_ID_REUSED. We do not silently return the first payment — that would let a client bug believe a second, different instruction had been executed |
| How long is a key remembered? | 48 hours for body-conflict detection. The payment itself keeps its key permanently, so a much later replay still resolves to the original payment rather than creating a second one |
| Scope | Global. One key binds to exactly one payment across every product and every provider. The 48-hour check compares the method, path and body, so the same key sent to instant-sepa-credit-transfers after sepa-credit-transfers is 409 REQUEST_ID_REUSED within 48 hours, and resolves to the first payment after that. Generate a fresh UUID v4 per instruction and this never arises |
| Other endpoints | X-Request-ID is required everywhere and is echoed back on every response. The 48-hour body check applies to every write (POST, PUT, DELETE), but only payment initiation is deduplicated: replaying a consent creation with the same key and body creates a second, separate consent request. On GETs it is a correlation id — quote it to support and we can find your exact request in the traceability log |
The one case that matters: do not reuse a key across retries of a request you changed. If a retry carries a different amount, payee or debtor, it is a new instruction and needs a new key. Reusing the key is refused rather than guessed at.
Status values
| Code | Meaning |
|---|---|
RCVD |
Received, not yet authorised |
ACTC |
Accepted, technical validation passed |
PDNG |
Pending — we are waiting on something before the payment can move, usually the customer confirming the payee. Poll it. It resolves to ACSP or RJCT, or to CANC if the customer does not come back within 24 hours. You may still cancel it yourself |
ACSP |
Accepted, settlement in process |
ACSC |
Accepted, settlement completed |
RJCT |
Rejected |
CANC |
Cancelled |
ACSP covers more than one internal state. A payment under compliance
review reports ACSP, exactly as one moving normally through the rails does.
This is deliberate and not negotiable: a distinguishable status would disclose
that a specific payment attracted scrutiny. Do not build logic that infers
anything beyond “in progress” from ACSP.
Cancellation
DELETE /v1/payments/{product}/{paymentId}
Possible only while the payment reads RCVD, ACTC or PDNG — the same point
at which the customer could still cancel it in their own portal. Past that point
you get CANCELLATION_INVALID (405). A paymentId that is not yours, or does
not exist, is 404 RESOURCE_UNKNOWN.
No customer authorisation is required, and there is no cancellation
authorisation sub-resource. Berlin Group allows an ASPSP to demand a second
ceremony for cancellation; we do not. The DELETE takes effect immediately and
returns the resulting transactionStatus.
The reasoning, so you can rely on it rather than infer it: cancelling only ever stops money moving. SCA exists to protect the customer from a payment they did not intend, and a cancellation cannot create one. Demanding a fresh ceremony would mean a customer who wants to stop a payment must first be available to authenticate — worst exactly when it matters.
Two limits keep that safe:
- You can only cancel a payment you initiated. Another provider’s payment is not addressable by you at all.
- You can only cancel it before it becomes final; after that the answer is
CANCELLATION_INVALID, not a reversal.
If you receive 403, the cancellation was refused because your registration or
certificate role does not permit it — not because the payment was uncancellable.
Why a payment can be refused
Initiation failures return PAYMENT_FAILED with one sentence, always the
same. Insufficient funds, an unknown debtor account, a limit, a compliance
hold — all identical from outside.
This is not unhelpfulness. A refusal that varied by cause would let anyone with a certificate probe our customers’ balances, limits and standing. The customer can see the real reason in their own portal, where they are authenticated as themselves.
In the sandbox, a creditor outside the test personas gets the same code with a sentence that says so — see the error catalogue.
Malformed requests are different and do say what is wrong: FORMAT_ERROR for a
bad IBAN, amount or TPP-Redirect-URI, PRODUCT_INVALID for a non-EUR
currency, PRODUCT_UNKNOWN for a product we do not offer.