Consents
A consent is the customer’s recorded permission for you to reach specific accounts for specific data, for a limited time.
What we accept
Dedicated accounts only. List the IBANs in the access object:
{
"access": {
"accounts": [{"iban": "FI2112345600000785"}],
"balances": [{"iban": "FI2112345600000785"}],
"transactions": [{"iban": "FI2112345600000785"}]
},
"recurringIndicator": true,
"validUntil": "2026-12-31",
"frequencyPerDay": 4
}
availableAccounts, availableAccountsWithBalance and allPsd2 are refused
with SERVICE_INVALID rather than silently narrowed — you must never believe
you received a broader grant than you did.
Which lists you fill decides the scope: accounts alone grants account details;
balances or transactions additionally grants balance and transaction data.
combinedServiceIndicator is not offered
Consents and payments are separate resources here, so there is no combined
AIS+PIS session. Sending combinedServiceIndicator: true is refused with
SERVICE_INVALID rather than quietly granting you an ordinary consent you would
then use as though it covered a payment. Omit the field or send false, and
create the consent and the payment separately.
Read the grant back — it may be narrower than you asked for
GET /v1/consents/{consentId} returns what was granted, not what was
requested, and the two can differ: the customer may authorise fewer accounts
than you named, and we cap validUntil silently.
{
"consentId": "PSXS-CONSENT-…",
"consentStatus": "valid",
"validUntil": "2026-12-28",
"recurringIndicator": true,
"frequencyPerDay": 4,
"access": {
"accounts": [{"iban": "FI21…"}],
"balances": [{"iban": "FI21…"}],
"transactions": [{"iban": "FI21…"}]
},
"_links": { "self": {"href": "…"}, "status": {"href": "…"} }
}
An omitted or empty list means that data type was not granted. If access
carries accounts but no balances, balance reads on that consent will be
refused — check here rather than discovering it at the first CONSENT_INVALID.
An IBAN you asked for that is absent from accounts does not belong to the
customer who authenticated; we do not say which, because that would answer a
question about a third party’s accounts.
A confirmation-of-funds consent carries account instead of access — it is
not a data-access grant (see funds confirmations).
Validity: the 180-day ceiling
Recurring consents last at most 180 days from the customer’s authentication
(RTS Art. 10a as amended by (EU) 2022/2360). Ask for less and we honour it; ask
for more and we cap it silently — read validUntil back from the consent rather
than assuming your request was taken literally.
The clock starts when the customer authenticates, not when you asked.
Frequency: the four-per-day cap
Unattended requests are limited to four per consent, per data type, per day (RTS Art. 36(5)) — so four account reads, four balance reads and four transaction reads, counted separately.
Attended requests (those carrying PSU-IP-Address) are not capped and do not
consume the unattended budget.
Exceeding it returns ACCESS_EXCEEDED (429). The counter resets daily.
What the cap applies to
Only reads of the customer’s data, and the funds-confirmation service:
| Operation | Capped? |
|---|---|
GET /v1/accounts, …/{id}, …/balances, …/transactions |
Yes — one budget per data type |
POST /v1/funds-confirmations |
Yes — a CBPII polling solvency is exactly what Art. 36(5) governs |
| Consent create / read / status / delete, and the authorisation reads | No |
Everything under /v1/payments |
No |
An uncapped operation returns no 429 and carries no X-RateLimit-* headers.
That is not an invitation to poll them: they are uncapped because they are
administrative rather than a window onto the customer’s data, and abuse is
handled as a registration matter rather than by a counter.
Knowing what is left before you spend it
Every consent-gated response carries the remaining budget, so you never have to burn a request to discover you are out:
X-RateLimit-Limit: 4
X-RateLimit-Remaining: 3
X-RateLimit-Reset: 1785283200
- Per data type, matching the cap itself. The numbers on a balances response describe the balances budget; they say nothing about transactions.
X-RateLimit-Resetis a Unix timestamp, not a duration — the next reset, which is a calendar boundary rather than a rolling window from first use.- On a
429you also getRetry-After(seconds). Wait it out; retrying sooner cannot succeed. - Attended requests do not consume the budget, so
Remainingis unchanged by them — but it still tells you what an unattended call would find.
Prefer these headers to a local counter. Yours cannot see calls made by your other processes or hosts, and it resets when you deploy; ours is the number the gate actually enforces.
frequencyPerDay and this cap are the same number
They are not two budgets. frequencyPerDay is the per-data-type unattended
allowance described above — four account reads and four balance reads and
four transaction reads, not four calls in total.
- Asking for more than 4 is capped, not refused — the same treatment as an
over-long
validUntil. Your consent is created; it just permits four. - Asking for fewer does not reduce it. We do not enforce a self-limit, so we do not claim one: the consent reports 4 either way. If you want to make fewer calls, make fewer calls.
- The granted consent reports what is enforced, not what you requested — 4 for a recurring consent, 1 for a one-off. Read it back; the number you get is the number that applies.
This is why a request for frequencyPerDay: 10 comes back as 4 rather than
as an error, and why it says 4 both before and after the customer authorises.
One-off consents (recurringIndicator: false)
A one-off consent is for a single errand — read the data once and be done. It behaves differently from a recurring one in three ways, and all three matter for how you build against it:
| Recurring | One-off | |
|---|---|---|
frequencyPerDay |
4 unattended accesses per data type, per day | 1 per data type. We force this value; requesting more does not change it |
| Lifetime | Up to 180 days from authentication | Ends shortly after you use it — see below |
| Supersession | A renewal supersedes the older consent | Never supersedes; there is nothing standing to replace |
“Spent” is not instantaneous, deliberately. A one-off consent usually backs
a sequence — accounts, then balances, then transactions — and retiring it the
moment the first call landed would break that sequence halfway. So the consent
is retired once it has been used and has been idle for one hour. Inside
that window you can complete a multi-leg read; after it, the consent is
expired and further calls return CONSENT_EXPIRED.
Note what that hour is not: it is not extra allowance. The one-per-data-type cap still applies, so the window lets you finish a read, not repeat one.
Attended requests (carrying PSU-IP-Address) are uncapped here as everywhere —
the cap governs unattended access. The idle clock still runs.
Which to ask for. If you will come back — an accounting integration, a
dashboard — ask for recurringIndicator: true and renew before validUntil. A
one-off is for a one-time check where asking the customer again is acceptable.
Status lifecycle
| Status | Meaning |
|---|---|
received |
Created, awaiting the customer’s authentication |
valid |
Authorised and usable |
expired |
Past validUntil, or a spent one-off consent (see above) |
revokedByPsu |
Ended by the customer or by PaySaxas |
terminatedByTpp |
Ended by you, or superseded by a newer consent of yours |
rejected |
The customer declined |
Note that revokedByPsu covers a compliance-driven revocation as well as a
customer-driven one. That is deliberate: we will not tell you which, because
the distinction can reveal facts about a customer that we may not disclose.
Renewal and supersession
To renew, create a new consent and send the customer through SCA again. When
they authorise it, any earlier recurring consent of yours covering the same
accounts moves to terminatedByTpp. A customer never accumulates several live
recurring grants to the same provider for the same accounts.
Consents held by other providers are untouched.
Revocation
Three ways a consent ends before its time:
- You terminate it —
DELETE /v1/consents/{consentId}. No notification is sent back; you are the party asking. - The customer revokes it in their PaySaxas portal, under Settings → Connected services. No ceremony is required of them — withdrawing a permission must not be harder than granting it.
- PaySaxas revokes it on support or compliance grounds.
In cases 2 and 3 we attempt a callback if you have registered one. The
callback is a courtesy, not the control: the consent stops working at the gate
immediately, whether or not we reach you. Build for CONSENT_INVALID arriving
without warning.
Registering a revocation callback
Self-service — no support ticket:
curl -X PUT https://xs2a.paysaxas.com/v1/revocation-callback \
--cert tpp-qwac.pem --key tpp-qwac.key \
-H "Content-Type: application/json" \
-H "X-Request-ID: 3b7e2c11-9f4d-4c2a-8f1e-6d5a4b3c2e10" \
-d '{"url": "https://your-app.example/psd2/revocation"}'
{
"url": "https://your-app.example/psd2/revocation",
"signatureHeader": "X-PaySaxas-Signature",
"signatureToleranceSeconds": 300,
"secret": "…"
}
secretis your HMAC key. It is derived from your registration rather than stored, so if you lose it,GET /v1/revocation-callbackreturns the same value — there is no reset ceremony.PUTdeclares state, so a repeat with the same URL changes nothing and a repeat with a different one replaces it.DELETEstops notifications.- Available to any registered provider, whatever your certificate authorises. Managing your own notifications is not an AIS, PIS or CBPII service, so it is not gated on one of those roles.
Until you register one, revocation notifications are marked “not required” and you will discover the change on your next call.
The callback body names the consent and its new status, and carries no reason:
{
"eventId": "b6f1…",
"consentId": "PSXS-CONSENT-3f2b...",
"consentStatus": "revokedByPsu",
"revokedAt": "2026-08-02T10:15:00Z"
}
Verifying the callback
Do not act on an unverified callback. The URL is registered privately, but
an obscure endpoint is not authentication: anyone who learned it and a
consentId could otherwise convince you a live consent was dead.
Every callback carries an HMAC-SHA256 signature:
X-PaySaxas-Signature: t=1785000000,v1=6c1f…
v1 is the hex HMAC-SHA256, keyed with your callback secret, over the
string "{t}.{raw request body}". To verify:
- Split the header into
tandv1. - Reject if
tis more than 300 seconds from your clock. - Recompute over
"{t}.{body}"using the raw bytes you received — not a re-serialised parse, which will not match. - Compare in constant time.
Your secret is issued with your callback URL; ask support to re-issue it. Rotations are announced in advance.
Other properties you can rely on:
| Transport | HTTPS only. We will not send to an http:// URL |
| Retries | Up to 5 attempts with exponential backoff, on connection failure or a non-2xx. Return 2xx to stop them |
| Replay | eventId is stable per revocation and repeated on every retry — dedupe on it. Two revocations can share a revokedAt second, so do not dedupe on that |
| Ordering | Not guaranteed. Treat each event as a statement about one consent, not as a sequence |
| Source addresses | Callbacks originate from our published egress ranges. Ask support for the current list if you allowlist by IP — but allowlisting is not a substitute for checking the signature |
If verification fails, drop the message and rely on the gate — a genuine revocation is already in force, so nothing is lost by ignoring a callback you cannot trust.