Scoped Mandate — one mandate, many purchases, one SCA
An AI agent shops on the customer's behalf. The customer grants the bank a limited mandate once, with Face ID; from then on the bank checks every payment against limits, merchants and validity, the merchant checks the product categories and signs an attestation, and the agent acts with its own identity and key — an identity the operator issues after the customer has identified there with the Konto Karte. The wallet never decides about payments; it shows the bank's state and can revoke.
Target picture: ARF, PaSO, SPAA Interface Specification API reference
At a glance
Three pictures, the whole idea: ① the customer allows once — that is the only approval. ② The agent shops without further prompts, within the limits. ③ Merchant and bank say no when something does not fit — and the customer always has the last word.
%%{init: {'look':'handDrawn','theme':'base','themeVariables':{
'fontFamily':'"Comic Neue","Patrick Hand",cursive','fontSize':'17px',
'primaryColor':'#ffffff','primaryBorderColor':'#3a3e45','primaryTextColor':'#2b2f36',
'lineColor':'#3a3e45','clusterBkg':'#fbfbfc','clusterBorder':'#b4b9c1','edgeLabelBackground':'#ffffff','titleColor':'#2b2f36'}}}%%
flowchart TB
subgraph P1["① Allow once"]
direction LR
N1["👤 Customer
“My agent may buy groceries:
up to €60 per purchase, €300 in total.”"]
W1["📱 Wallet
shows everything —
Face ID, a single time"]
B1["🏦 Bank
holds the mandate
and the limits"]
N1 --> W1 --> B1
end
subgraph P2["② The agent shops"]
direction LR
A2["🤖 Agent
“2 milk, bread, dish soap”"]
H2["🧺 Kiezmarkt
checks the categories,
stamps the basket"]
B2["🏦 Bank
checks the limits,
pays via SCT Inst"]
R2["🧾 Receipt
to the customer"]
A2 --> H2 --> B2 --> R2
end
subgraph P3["③ Limits hold"]
direction LR
X3["🍺 “… and a beer?”"] -- “No”, says the merchant --> H3["🛒"]
Y3["💸 More than €60?"] -- “No”, says the bank --> B3["🏦"]
Z3["👤 “Revoke!”"] -- any time --> B4["🏦 no more payments"]
end
P1 ~~~ P2 ~~~ P3
classDef nutzer fill:#fff0f2,stroke:#e60013,stroke-width:2px
classDef bank fill:#ffe5e8,stroke:#e60013,stroke-width:2px
classDef wallet fill:#eef2ff,stroke:#3a3e45
classDef agent fill:#f3f4f6,stroke:#3a3e45
classDef haendler fill:#e6f6f0,stroke:#0f8b8d,stroke-width:2px
classDef beleg fill:#fffbe6,stroke:#b26a00
class N1,Z3 nutzer
class B1,B2,B3,B4 bank
class W1 wallet
class A2 agent
class H2,H3 haendler
class R2 beleg
class X3,Y3 agentIn detail: the flow as a sequence diagram
Status 23 October 2026 (design revision v3, live since 22 October; categories as Google Product Taxonomy IDs since 23 October): flow A obtains the credentials via OpenID4VCI — the Konto Karte after logging in to online banking, the agent credential from the operator after the customer identified there with the Konto Karte (OpenID4VP). Flow B is the one SCA: the wallet presents the Konto Karte via OpenID4VP in the PaSO profile to the bank's verifier, with the mandate as transaction_data in the request; the bank then issues the approved mandate as a credential to the agent's key. Flow C is the purchase: the agent presents its agent credential and the bank's mandate credential to the merchant via OpenID4VP and hands over the checkout part in the clear; the basket hash is the transaction_data, the merchant verifies both credentials and recomputes hash C — no call to the bank. Flow D: rejection and revocation via the status list. Every decision lies with bank or merchant — the wallet shows and confirms, the agent executes. Numbers = message order; click to enlarge.
%%{init: {'theme':'base','themeVariables':{
'fontFamily':'Sparkasse, system-ui, sans-serif','fontSize':'14px',
'primaryColor':'#ffe5e8','primaryBorderColor':'#e60013','primaryTextColor':'#2b2f36',
'actorBkg':'#f3f4f6','actorBorder':'#9aa0a8','actorTextColor':'#2b2f36',
'signalColor':'#3a3e45','signalTextColor':'#3a3e45',
'noteBkgColor':'#fff0f2','noteBorderColor':'#e60013','noteTextColor':'#2b2f36',
'labelBoxBkgColor':'#f3f4f6','labelBoxBorderColor':'#9aa0a8','labelTextColor':'#2b2f36',
'loopTextColor':'#515761','activationBkgColor':'#ffe5e8','activationBorderColor':'#e60013',
'sequenceNumberColor':'#fff'}}}%%
sequenceDiagram
autonumber
actor N as Customer
participant W as Wallet (EUDIPal)
participant A as Agent (in EUDIPal)
participant O as Operator — issuer (operator.s-you.me)
participant I as Bank — issuer
participant B as Bank — verifier + mandate service
participant H as Merchant
rect rgb(243, 244, 246)
Note over N,I: Flow A — obtain credentials (OpenID4VCI, once)
N->>W: fetch the Konto Karte (bank offer)
W->>I: PAR + authorization code, login at online banking
I-->>W: SD-JWT VC "Konto Karte" (cnf = wallet key, status list)
W->>O: present the Konto Karte (OpenID4VP, key binding) — the customer identifies to the operator
O-->>A: offer for the agent credential (pre-authorized code)
A->>O: proof with the agent key
O-->>A: SD-JWT VC "agent identity" (cnf = agent key, operator root)
end
rect rgb(255, 240, 242)
Note over N,B: Flow B — one mandate, one SCA (OpenID4VP in the PaSO profile)
N->>A: "buy milk, bread, dish soap"
A->>A: build the checkout part (categories) → hash C
A->>B: POST /agent-nonces
A->>B: POST /mandate-requests (payment part, hash C, agent VC + KB-JWT)
B->>B: verify agent VC (operator on trust list, status, cnf, nonce) · create verifier session
B-->>A: transaction_data (scoped_mandate:2) + openid4vp_url
A->>W: consent: payment part (bank) + checkout part (local)
W->>N: show both halves, hash C verified
N->>W: Face ID
W->>B: GET /paso/request/{state} → JAR (DCQL, transaction_data)
W->>W: JAR entry == displayed data? select the Konto Karte
W->>B: POST /paso/response/{state} — JWE: Konto Karte + KB-JWT (nonce, aud, hash, 9 PaSO claims)
B->>B: anchor, status list, cnf, hash over the entry, claims → mandate (sca_path b) + credential offer
W->>B: GET /paso/sessions/{state} → mandate_id + credential_offer
A->>I: OpenID4VCI pre-authorized code, proof with the agent key
I-->>A: SD-JWT VC "mandate" (payment part, hash C, sca, cnf = agent key, status list)
B-->>W: mandate (mirror: status, limits, hash C, credential ref)
end
rect rgb(243, 244, 246)
Note over A,H: Flow C — purchase without a prompt (OpenID4VP at the merchant)
A->>H: GET /catalog?q=…
H-->>A: items, prices, categories
A->>H: POST /checkout/sessions (basket, mandate_id)
H-->>A: JAR: DCQL agent credential + mandate credential, transaction_data = basket hash + mandate
A->>A: verify JAR signature, transaction_data == own basket?
A->>H: POST …/response — agent VC + mandate VC, each with KB-JWT (transaction_data_hashes), checkout part in the clear, JWE
H->>H: chains to operator root and bank root, status lists, key binding, sha256(checkout part) == hash C, categories
H-->>A: signed attestation (agent_credential_ok, mandate_credential_ok, checkout_hash_ok)
A->>B: POST /payments (attestation, amount, agent_proof)
B->>B: merchant signature, hash C = SCA hash, limits, budget
B-->>A: receipt — SCT Inst, remaining budget
A-->>N: items, total, receipt
end
rect rgb(255, 240, 242)
Note over N,H: Flow D — rejection and revocation
alt merchant rejects (e.g. beer, category_excluded)
H-->>A: 422 rejected_by: merchant
A-->>N: "Merchant: alcohol is excluded by the mandate"
else bank rejects (e.g. limit_exceeded)
B-->>A: 422 rejected_by: bank
A-->>N: "Bank: amount above the per-purchase limit"
end
N->>B: revoke (wallet, bank app, dashboard)
B->>B: status bit of the mandate credential set
B-->>N: mandate void — no further payment, merchant sees it on the status list
endAs downloadable images: interaction across all four flows · sequence of the mandate approval. Step by step: Interface Specification §1.14 (mandate) and §2.5 (checkout).
The five parties
Status and outlook
The whole flow runs end to end, live, on OpenID4VC, with five parties: the bank (Sparkasse Agentenstadt) issues the customer's Konto Karte (SCA credential) via OpenID4VCI. With this card the customer identifies to the agent's operator (OpenID4VP), and only then does the operator issue the agent's identity credential — the app issues no agent identity itself, and without a Konto Karte there is no agent. The customer grants the bank one limited mandate by presenting the Konto Karte via OpenID4VP in the PaSO profile to the bank's verifier (Face ID as the factor); there is no other approval path. New since 21 October (v3, live since 22 October): the bank then issues the approved mandate as a credential to the agent's key — payment part in the clear, the categories only as a hash, revocation via the bank's status list. Since 23 October the categories are Google Product Taxonomy IDs, so agent, customer and merchant mean the same thing by a category. Afterwards the AI agent shops autonomously at Kiezmarkt in the chat; the bank checks every payment against the mandate, the merchant verifies the bank's mandate credential and the agent's credential, recomputes the category hash and checks the basket — without asking the bank — and every rejection names party and reason. Bank, merchant and operator each have their own screen; all interfaces are documented as a specification and as OpenAPI 3.1.

Detailed status, open items with effort and decisions: internal area (sign in).
Documents
API reference
Machine-readable for partners: OpenAPI 3.1 rendered with Redoc; the raw YAML is here and at every service under /openapi.yaml.
https://bank.s-you.me · YAML · liveOpenAPI 3.1Merchant — Merchant Service APIDiscovery with signing key, catalog, OpenID4VP checkout, attestation, check log. OpenAPI 3.1.https://shop.s-you.me · YAML · liveLive systems
Publicly reachable, no login. Everything runs in Frankfurt (Google Cloud Run, europe-west3).
Quick start
curl -s https://bank.s-you.me/.well-known/mandate-service | jq .
curl -s 'https://shop.s-you.me/catalog?q=milch' | jq .
# full bank flow B/C/D with a real P-256 agent key and an operator-issued agent credential
uv run --python 3.12 --with 'cryptography==43.0.3' --with 'jwcrypto==1.5.6' \
python tools/mock-bank/smoke_test.py https://bank.s-you.me https://operator.s-you.me
In the app (build ≥ 795): Settings → Labor → "Einkaufs-Agent (Test)", three steps — fetch the Konto Karte (demo login demo / 12345), set up the shopping agent (identify to the operator with the card), grant the mandate (consent sheet → Face ID; the bank issues the mandate credential). Then chat → "Kauf mir 2 Milch, Brot und Spülmittel" → receipt.
"Und ein Bier dazu" → the merchant rejects, the agent says who and why. "Was hat der Agent gekauft?" → the bank's log.
"Widerrufe das Mandat" → the bank flips the mandate credential's status bit and rejects every further payment.