myTU · Open Banking API · v1
Production psd2.mytu.live Sandbox psd2.mytu.dev
PSD2 · Berlin Group NextGenPSD2

myTU PSD2 Open Banking API (XS2A)

The dedicated PSD2 access interface that myTU exposes under /psd2/v1 for licensed Third-Party Providers - payment-initiation (PISP) and account-information (AISP) services.

Version v1 eIDAS mTLS · QWAC + QSEAL Payment Initiation (PIS) Account Information (AIS)

Overview

This specification describes the dedicated PSD2 access interface (XS2A) that myTU (Travel Union UAB, EMI permit No LB001966, Bank of Lithuania) exposes under /psd2/v1, independent of the myTU Business API. It covers two services: Payment Initiation (PIS) and Account Information (AIS).

Where practical the message shapes and status vocabulary follow the Berlin Group NextGenPSD2 convention, so a TPP that already integrates other European ASPSPs can reuse its tooling. myTU implements a pragmatic subset, documented in full here - if a field is not listed in these docs, do not rely on it.

Audience. Licensed Third-Party Providers (TPPs) acting as payment-initiation (PISP) and/or account-information (AISP) service providers.

Two services, one interface

  • Payment Initiation (PIS) - start a payment, hand the payer an approval link, and learn the outcome by polling the status endpoint.
  • Account Information (AIS) - with the customer's consent, read accounts, balances and transactions for up to 90 days.

Both services reuse the same authentication (QWAC + QSEAL) and the same in-app approval (SCA) through the customer's own myTU app.

at a glance
# Base URL (sandbox)
https://psd2.mytu.dev/psd2/v1

# Authentication - no API keys
#   · eIDAS QWAC  → mutual TLS (the connection)
#   · eIDAS QSEAL → request signing (the instruction)

# Payment Initiation (PIS)
POST   /payments/{payment-product}
GET    /payments/{payment-product}/{id}/status
DELETE /payments/{payment-product}/{id}

# Account Information (AIS)
POST   /consents
GET    /accounts
GET    /accounts/{resourceId}/balances
GET    /accounts/{resourceId}/transactions

Base URLs & versioning

All endpoints live under the /psd2/v1 prefix. Aggregators can use https://psd2.mytu.live/psd2/v1 as the base URL; the paths beneath it follow the Berlin Group relative layout.

EnvironmentBase URL
Productionhttps://psd2.mytu.live
Sandboxhttps://psd2.mytu.dev

Integrate and test against the sandbox with your eIDAS test certificates; it mirrors production behaviour. The interface is versioned by the /psd2/v1 path prefix - breaking changes ship under a new prefix.

Paths are absolute and always start with /psd2/v1. _links hrefs in responses use the same absolute form.
example request target
POST https://psd2.mytu.live/psd2/v1\
/payments/sepa-credit-transfers

Conventions

These rules hold everywhere in the API. Read them once and they apply to every endpoint below.

TopicRule
Paths Absolute and always start with the /psd2/v1 prefix (e.g. POST /psd2/v1/consents).
Monetary amounts Decimal strings ("123.45"), never numbers. In transactions a debit is negative, a credit is positive.
Currencies ISO-4217 ("EUR").
Dates & timestamps Dates are ISO-8601 calendar dates ("2026-09-30"); timestamps are full ISO-8601 ("2026-06-30T14:00:05Z").
Ids paymentId, consentId, resourceId are opaque strings - never parse them.
X-Request-ID A UUID required on every request and echoed on every response.
Authentication

eIDAS mutual TLS (QWAC)

There are no API keys, client secrets, or bearer tokens. A TPP authenticates by presenting a valid eIDAS QWAC certificate at the TLS handshake (mutual TLS).

  • The certificate must carry an organisationIdentifier (OID 2.5.4.97) - the regulator-issued PSD2 authorisation number, e.g. PSDLT-BL-123456 - and the clientAuth extended key usage.
  • The TPP's role (PISP / AISP) is taken from the certificate's PSD2 QcStatement. Calling a PIS endpoint requires the PISP role; an AIS endpoint requires the AISP role.
  • The certificate identity establishes who the caller is. No further registration step is required.
A request without a valid certificate is rejected at the connection level (401).
mutual-TLS call
curl https://psd2.mytu.dev/psd2/v1/accounts \
  --cert  qwac.pem \
  --key   qwac.key \
  -H "X-Request-ID: 4f8e2c1a-..." \
  -H "Consent-ID: a1b2c3d4e5f6..."
QWAC secures the connection. Money instructions and access grants are additionally signed with QSEAL - see below.
Authentication

Request signing (QSEAL)

QWAC secures the connection. The payment-initiation POST carries a money instruction, and the consent-creation POST carries an access-authorization grant, so both must additionally be signed - their content is then integrity-protected and non-repudiable. The TPP signs with its eIDAS QSEAL (qualified electronic seal) certificate.

Three headers are required on the payment-initiation POST and the consent-creation POST - the requests that carry an instruction or a grant. Plain GET reads and DELETE are not signed - they carry no instruction, and nothing is read or executed without a valid consent or the customer's SCA.

HeaderContent
Digest SHA-256=<base64> - hash of the raw request body.
Signature The signature (HTTP Message Signatures form: keyId, algorithm, headers, signature) covering at least Digest, X-Request-ID, and the request target.
TPP-Signature-Certificate The base64-encoded QSEAL certificate to verify against.

What we verify

  • The body hash matches Digest.
  • The Signature validates against the QSEAL public key.
  • The QSEAL is a valid eIDAS seal certificate.
  • It belongs to the same TPP as the QWAC (same organisationIdentifier).

The verified signature is retained for audit / non-repudiation. A missing or invalid signature is rejected (SIGNATURE_MISSING / SIGNATURE_INVALID).

This is separate from, and in addition to, the customer's in-app authentication (SCA): the TPP seals the instruction; the customer authorizes it.
required signing headers
Digest: SHA-256=47DEQpj8HBSa+/TImW+5JCeuQ...
Signature: keyId="...",
  algorithm="...",
  headers="digest x-request-id (request-target)",
  signature="..."
TPP-Signature-Certificate: MIIE...QSEAL...==
Payment initiation · PIS

How payment initiation works

myTU authorises payments through the customer's own myTU app - not through a web login on our side. The model is decoupled: you start a payment, we hand you back a link, the customer approves it inside the myTU app, and you learn the outcome by polling the status endpoint.

1
Initiate

TPP → POST /psd2/v1/payments/… → myTU returns a paymentId + a link, status RCVD.

2
Hand off the approval link

Send the payer's browser to the link. On a phone it opens the myTU app directly; on a desktop the page shows a QR the payer scans with the myTU app.

3
Customer approves & signs

The payer approves and signs the payment inside the myTU app (SCA).

4
Poll for the outcome

TPP → GET …/statusRCVD → ACSP → ACSC, or RJCT/CANC.

Key properties

  • Every call is immediate. Requests return at once; there is no long-polling and no held-open connection. The wait for the human is covered by you polling the status.
  • The payer identifies themselves by approving in their own app. You do not need to know which myTU customer they are.
  • debtorAccount is not required and is not used to route the request. If you send it, it is treated only as a constraint (the approving customer's account must match it) - it never determines who is asked to approve. Never assume that supplying an IBAN will prompt that account holder; it will not.

The approval link

The initiation response contains an scaRedirect link - a myTU-hosted web page (served from the public host mytu.live, or mytu.dev in the sandbox - not the psd2. API host). You do not build a QR yourself. Just send the payer's browser to that link (HTTP redirect, a button, or embed it in an iframe); the page adapts to the device:

  • Mobile - the page opens the myTU app directly on the payment-approval screen. No scanning needed.
  • Desktop - the page itself renders a QR code for the payer to scan with their myTU app. You can poll the status endpoint alongside it and update your page when the payment becomes final. If you supplied TPP-Redirect-URI, the customer is returned there after approval.
The QR encodes an internal myTU scan token, not the link's URL - but you never deal with that. Always hand the customer the scaRedirect link as-is; do not generate your own QR from it.

The link is single-use: once the payment is approved, cancelled, or expired, opening it again only shows the final state.

Lifetime

An initiated-but-unapproved payment expires after roughly 5 minutes of inactivity - the window refreshes when the customer opens the approval link and stays alive while they are actively approving, so a customer mid-signature is never cut off. After expiry the status becomes RJCT. The paymentId and its status remain queryable after expiry and after settlement.

1.  TPP   POST /psd2/v1/payments/…
          → paymentId + link  (RCVD)

2.  TPP   send payer's browser → link
        • phone   → opens myTU app
        • desktop → page shows a QR

3.  payer approves & signs in app

4.  TPP   GET …/status   (poll)
          RCVD → ACSP → ACSC
          or → RJCT / CANC
Payment initiation · PIS

Initiate a payment

POST /psd2/v1/payments/{payment-product}

{payment-product} is one of:

ProductMeaning
sepa-credit-transfersStandard SEPA credit transfer (EUR).
instant-sepa-credit-transfersSEPA Instant credit transfer (EUR).

Instant vs regular

  • sepa-credit-transfers is best-effort instant - we execute over the SCT Inst rail whenever the beneficiary bank is reachable and the amount qualifies, otherwise as a normal SCT. The customer simply gets it faster when possible; never rejected for instant being unavailable.
  • instant-sepa-credit-transfers is enforced - if the beneficiary bank is not reachable for SCT Inst, or the amount exceeds the instant cap, the request is rejected (400 PRODUCT_INVALID); it is never silently downgraded to a regular transfer. Availability is checked at initiation (early reject) and again enforced at execution.

Headers

HeaderReq.Notes
X-Request-ID yes UUID; echoed back on every response. Idempotency key (1-hour window): retrying with the same X-Request-ID and the same body replays the original 201; the same X-Request-ID with a different body is rejected 409 REQUEST_ID_REUSE.
DigestyesSHA-256=<base64> of the body - see Request signing.
SignatureyesQSEAL signature over Digest + X-Request-ID + target.
TPP-Signature-Certificateyesbase64 QSEAL certificate.
TPP-Redirect-URInoWhere to return the customer's browser after approval.
PSU-IP-AddressnoThe payer's IP, when the payer is present in your flow.
Content-Typeyesapplication/json

Request body

  • amount is a decimal string.
  • creditorAccount.iban and creditorName are required.
  • remittanceInformationUnstructured is optional, max 140 characters.
  • debtorAccount ({ "iban": "…" }) is optional and not used for routing.

Deliver _links.scaRedirect.href to the payer as described in How payment initiation works.

curl -X POST \
  https://psd2.mytu.dev/psd2/v1/payments/sepa-credit-transfers \
  --cert qwac.pem --key qwac.key \
  -H "X-Request-ID: 4f8e2c1a-9b7d-4f0e-8a2b-1c3d4e5f6071" \
  -H "Content-Type: application/json" \
  -H "Digest: SHA-256=47DEQpj8HBSa+/TImW+5JCeuQ..." \
  -H 'Signature: keyId="...",algorithm="...",headers="digest x-request-id (request-target)",signature="..."' \
  -H "TPP-Signature-Certificate: MIIE...==" \
  -d '{
    "instructedAmount": { "currency": "EUR", "amount": "123.45" },
    "creditorAccount":  { "iban": "LT121000011101001000" },
    "creditorName":     "Merchant Ltd",
    "remittanceInformationUnstructured": "Invoice 12345"
  }'
{
  "instructedAmount": { "currency": "EUR", "amount": "123.45" },
  "creditorAccount":  { "iban": "LT121000011101001000" },
  "creditorName":     "Merchant Ltd",
  "remittanceInformationUnstructured": "Invoice 12345"
}
{
  "transactionStatus": "RCVD",
  "paymentId": "b3f1c2a4e5d647809a1b2c3d4e5f6071",
  "_links": {
    "scaRedirect": {
      "href": "https://mytu.live/sca.pi.html?p=b3f1c2a4e5d647809a1b2c3d4e5f6071"
    },
    "status": {
      "href": "/psd2/v1/payments/sepa-credit-transfers/b3f1c2a4e5d647809a1b2c3d4e5f6071/status"
    },
    "self": {
      "href": "/psd2/v1/payments/sepa-credit-transfers/b3f1c2a4e5d647809a1b2c3d4e5f6071"
    }
  }
}
Response is 201 Created with transactionStatus: "RCVD".
Payment initiation · PIS

Get payment status

GET /psd2/v1/payments/{payment-product}/{paymentId}/status

Poll this until a final status. A reasonable cadence is every 2-5 seconds while the customer is approving.

Treat any non-final status as “keep polling”. Treat RJCT/CANC as terminal failures and ACSC as success.
curl https://psd2.mytu.dev/psd2/v1\
/payments/sepa-credit-transfers\
/b3f1c2a4e5d647809a1b2c3d4e5f6071/status \
  --cert qwac.pem --key qwac.key \
  -H "X-Request-ID: 7a2b1c3d-..."
{ "transactionStatus": "ACSP" }
Payment initiation · PIS

Get payment details

GET /psd2/v1/payments/{payment-product}/{paymentId}

Returns the submitted payment data plus the current transactionStatus.

curl https://psd2.mytu.dev/psd2/v1\
/payments/sepa-credit-transfers\
/b3f1c2a4e5d647809a1b2c3d4e5f6071 \
  --cert qwac.pem --key qwac.key \
  -H "X-Request-ID: 5e9d8c7b-..."
{
  "transactionStatus": "ACSC",
  "paymentId": "b3f1c2a4e5d647809a1b2c3d4e5f6071",
  "instructedAmount": { "currency": "EUR", "amount": "123.45" },
  "creditorAccount":  { "iban": "LT121000011101001000" },
  "creditorName":     "Merchant Ltd",
  "remittanceInformationUnstructured": "Invoice 12345"
}
Payment initiation · PIS

Cancel a payment

DELETE /psd2/v1/payments/{payment-product}/{paymentId}

Cancels a payment before the customer has started approval. The response returns transactionStatus: "CANC". Once the customer has opened the payment in the app, or the payment is already final, it can no longer be cancelled (409).

After cancellation the payment is terminated; a subsequent status poll reports it as a terminal failure (RJCT) - you already have the authoritative CANC from this response.

curl -X DELETE \
  https://psd2.mytu.dev/psd2/v1\
/payments/sepa-credit-transfers\
/b3f1c2a4e5d647809a1b2c3d4e5f6071 \
  --cert qwac.pem --key qwac.key \
  -H "X-Request-ID: 9f1e2d3c-..."
{ "transactionStatus": "CANC" }
Payment initiation · PIS

Payment status values

The transactionStatus field moves through these codes.

CodeMeaningFinal?
RCVDReceived - initiated, awaiting customer approval.no
ACTCAccepted Technical Validation - approval in progress.no
ACSPAccepted Settlement in Process - customer approved, executing.no
ACSCAccepted Settlement Completed.yes · success
RJCTRejected - declined, failed, or expired.yes · failure
CANCCancelled by the TPP - returned by the DELETE response (a later status poll reports RJCT).yes · failure
Account information · AIS

How account access works

AIS lets an authorised AISP read a customer's myTU account data - accounts, balances, transactions - with the customer's consent. The model is consent-based and decoupled, and reuses the same authentication (QWAC + QSEAL) and the same in-app approval (the scan/SCA link) as PIS. The shape is different from a payment, though: a payment is one-shot; a consent is a standing grant the AISP reuses for up to 90 days.

1
Create a consent

TPP → POST /psd2/v1/consents → myTU returns a consentId + a link, status received.

2
Hand off the approval link

Send the customer's browser to the link - phone opens the myTU app on the consent screen; desktop shows a QR to scan.

3
Customer approves (SCA)

The customer reviews the requested access & approves in the myTU app → consent valid.

4
Read account data

TPP reads account data carrying the Consent-ID header, repeatedly within the validity.

Key properties

  • A consent is a standing grant. Once valid, the AISP reuses it (no fresh approval per call) for up to its validUntil, which myTU caps at 90 days.
  • “Customer present” vs “unattended.” When the customer is in the loop, send the PSU-IP-Address header - that access is unlimited. Unattended polling (no PSU-IP-Address) is limited to frequencyPerDay retrievals.
  • The customer identifies themselves by approving. As with PIS, you do not need to know which myTU customer they are; the consent binds to whoever approves it.
  • The customer chooses which account at approval - their personal account or a business they represent - so a consent is bound to one account (which may be a company). If you named a specific IBAN in access, the customer must select a matching account; with allPsd2 / availableAccounts they choose freely.
  • You never deal with the IBAN as an id. GET /psd2/v1/accounts returns an opaque resourceId; use that in the balance/transaction calls.
AIS endpoints at a glance
# consent - addressed by id in the PATH
POST   /psd2/v1/consents            (QSEAL)
GET    /psd2/v1/consents/{id}
GET    /psd2/v1/consents/{id}/status
DELETE /psd2/v1/consents/{id}

# account data - Consent-ID HEADER required
GET /psd2/v1/accounts
GET /psd2/v1/accounts/{resourceId}/balances
GET /psd2/v1/accounts/{resourceId}/transactions
All AIS endpoints require the QWAC + the AISP role. Every account-data call requires the consent to be valid.
Account information · AIS

List accounts

GET /psd2/v1/accounts[?withBalance=true]

Header Consent-ID (required) + the QWAC. Returns the accounts the consent grants. This is the entry point of every retrieval - call it first to obtain each account's resourceId, then use that id in the balance/transaction calls. A resourceId is stable for the life of the consent, so you may store it.

?withBalance=true embeds balances inline (only if the consent grants balances); an availableAccounts-only consent lists accounts but never balances or transactions.

A myTU account is multi-currency - one IBAN holding balances in several currencies - so a single account may expose multiple currency balances. Use the resourceId (not the IBAN) in the calls below.
curl https://psd2.mytu.dev/psd2/v1/accounts \
  --cert qwac.pem --key qwac.key \
  -H "X-Request-ID: 1a2b3c4d-..." \
  -H "Consent-ID: a1b2c3d4e5f6..."
{
  "accounts": [
    {
      "resourceId": "0fa3...",
      "iban": "LT121000011101001000",
      "currency": "EUR",
      "name": "ACME UAB",
      "ownerName": "ACME UAB",
      "balances": [ /* present only with ?withBalance=true */ ]
    }
  ]
}
Account information · AIS

Balances

GET /psd2/v1/accounts/{resourceId}/balances

Header Consent-ID. Requires the consent's access to cover balances for this account. A myTU account is multi-currency, so balances[] carries one entry per currency held; balanceType is interimAvailable (spendable now).

curl https://psd2.mytu.dev/psd2/v1\
/accounts/0fa3.../balances \
  --cert qwac.pem --key qwac.key \
  -H "X-Request-ID: 2b3c4d5e-..." \
  -H "Consent-ID: a1b2c3d4e5f6..."
{
  "account": { "iban": "LT121000011101001000" },
  "balances": [
    {
      "balanceType": "interimAvailable",
      "balanceAmount": { "currency": "EUR", "amount": "1234.56" }
    },
    {
      "balanceType": "interimAvailable",
      "balanceAmount": { "currency": "USD", "amount": "0.00" }
    }
  ]
}
Account information · AIS

Transactions

GET /psd2/v1/accounts/{resourceId}/transactions

Header Consent-ID. Requires the consent's access to cover transactions for this account.

Query parameters

ParamDescription
dateFrom reqISO date; start of the window.
dateTo optISO date, defaults to today; inclusive.
bookingStatus opt booked (default), pending, or both.
  • booked = settled ledger entries within the date window.
  • pending = the account's outgoing in-flight transfers (not yet settled). These reflect current state, so they are returned in full regardless of the date window.
  • transactionAmount.amount is signed: debits negative, credits positive (always EUR - the amount that moved on the account; a cross-border transfer shows the EUR charged, not the destination currency).
  • If the response includes transactions._links.next, follow it for the next page; its absence means the set is complete. Narrow the date window for very large histories.
curl "https://psd2.mytu.dev/psd2/v1\
/accounts/0fa3.../transactions\
?dateFrom=2026-06-01&dateTo=2026-06-30\
&bookingStatus=booked" \
  --cert qwac.pem --key qwac.key \
  -H "X-Request-ID: 3c4d5e6f-..." \
  -H "Consent-ID: a1b2c3d4e5f6..."
{
  "account": { "iban": "LT121000011101001000" },
  "transactions": {
    "booked": [
      {
        "transactionId": "...",
        "bookingDate": "2026-06-14",
        "valueDate": "2026-06-14",
        "transactionAmount": { "currency": "EUR", "amount": "-42.00" },
        "creditorName": "Merchant Ltd",
        "remittanceInformationUnstructured": "Invoice 12345"
      }
    ],
    "pending": []
  }
}
Account information · AIS

Access limits

  • Validity - up to 90 days (validUntil); after that the AISP must obtain a new consent.
  • Frequency - frequencyPerDay caps unattended retrievals per day. Send PSU-IP-Address when the customer is present to access without counting against the cap. Over the cap → 429 ACCESS_EXCEEDED.
  • One “retrieval” is a logical access (the set of calls to assemble a view), not each individual GET.
Reference

Errors

Every error returns the appropriate HTTP status with a Berlin Group tppMessages body.

{
  "tppMessages": [
    {
      "category": "ERROR",
      "code": "FORMAT_ERROR",
      "text": "creditorAccount.iban is required"
    }
  ]
}

category is always ERROR; text is a human-readable detail - branch on code + HTTP status, not on text. The complete set of codes:

CodeHTTPWhen
FORMAT_ERROR400Malformed JSON, or a missing / invalid field.
PRODUCT_INVALID400instant-sepa-credit-transfers not possible for this beneficiary or amount.
SIGNATURE_MISSING400A required QSEAL header (Digest / Signature / TPP-Signature-Certificate) is absent.
CERTIFICATE_MISSING401No client certificate presented at the TLS handshake.
CERTIFICATE_INVALID401Not a valid eIDAS QWAC, or a missing / mismatched organisationIdentifier.
SIGNATURE_INVALID401The QSEAL signature does not verify, or does not cover the required headers.
CONSENT_INVALID401 / 403Consent unknown, not yet approved, revoked, owned by another TPP, or does not cover the requested account / data.
CONSENT_EXPIRED401Consent is past its validUntil.
ROLE_INVALID403The certificate lacks the required role (PISP for payments, AISP for AIS).
RESOURCE_UNKNOWN404Unknown paymentId, account resource, or path.
CONSENT_UNKNOWN404Unknown consentId.
PRODUCT_UNKNOWN405Unsupported {payment-product}.
REQUEST_ID_REUSE409X-Request-ID reused with a different body.
CANCELLATION_INVALID409The payment can no longer be cancelled (approval started, or already final).
ACCESS_EXCEEDED429The consent's frequencyPerDay for unattended access was reached.
INTERNAL_SERVER_ERROR500Unexpected server error - safe to retry later.
A bare 429 (no tppMessages) may also be returned by overall rate limiting; back off and retry.
Reference

Status callbacks

By default you poll the status endpoint. On request, myTU can additionally push a signed notification to a URL you register, on each status change, so you do not have to poll aggressively. Polling remains the source of truth.

Contact myTU to enable status callbacks for your TPP.
poll vs push
# default - you poll (the source of truth)
GET /psd2/v1/payments/.../status

# optional - myTU pushes a signed notification
# on each status change to a URL you register
your-tpp.example/psd2-callback
Reference

Out of scope for v1

The following are not part of this version and must not be relied on:

  • Bulk payments, periodic / standing-order payments, future-dated payments.
  • Funds confirmation (PIIS / CBPII).
  • Card-account access (/psd2/v1/card-accounts).
  • Transactions as a downloadable camt.053 file (JSON only).

Any of these may be added in a future version under the same interface and authentication model.