Scoped Mandate — ein Mandat, viele Einkäufe, eine SCA

Ein KI-Agent kauft im Namen des Nutzers ein. Der Nutzer erteilt der Sparkasse einmal per Face ID ein begrenztes Mandat; danach prüft die Bank jede Zahlung gegen Limits, Händler und Gültigkeit, der Händler prüft die Warengruppen und signiert eine Attestation, der Agent handelt mit eigener Identität und eigenem Schlüssel. Die Wallet entscheidet nie über Zahlungen — sie zeigt den Stand der Bank und kann widerrufen.

Zielbild: ARF, PaSO, SPAA   Interface Specification   API-Referenz

Auf einen Blick

Drei Bilder, die ganze Idee: ① Der Nutzer erlaubt einmal — das ist die einzige Freigabe. ② Der Agent kauft ein, ohne Rückfrage, innerhalb der Grenzen. ③ Händler und Bank sagen Nein, wenn etwas nicht passt — und der Nutzer hat jederzeit das letzte Wort.

%%{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["① Einmal erlauben"]
    direction LR
    N1["👤 Nutzer
„Mein Agent darf Lebensmittel kaufen:
bis 60 € je Einkauf, 300 € gesamt.“"] W1["📱 Wallet
zeigt alles an —
Face ID, ein einziges Mal"] B1["🏦 Sparkasse
hält das Mandat
und die Grenzen"] N1 --> W1 --> B1 end subgraph P2["② Der Agent kauft ein"] direction LR A2["🤖 Agent
„2 Milch, Brot, Spülmittel“"] H2["🧺 Kiezmarkt
prüft die Warengruppen,
stempelt den Korb"] B2["🏦 Sparkasse
prüft die Grenzen,
zahlt per SCT Inst"] R2["🧾 Beleg
an den Nutzer"] A2 --> H2 --> B2 --> R2 end subgraph P3["③ Grenzen gelten"] direction LR X3["🍺 „… und ein Bier?“"] -- „Nein“, sagt der Händler --> H3["🛒"] Y3["💸 Mehr als 60 €?"] -- „Nein“, sagt die Bank --> B3["🏦"] Z3["👤 „Widerrufen!“"] -- jederzeit --> B4["🏦 keine Zahlung mehr"] 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 agent

Im Detail: der Ablauf als Sequenzdiagramm

Flows A (Agent-Identität, einmalig), B (Mandat und SCA), C (Einkauf) und D (Ablehnung, Widerruf). Jede Entscheidung liegt bei Bank oder Händler — die Wallet zeigt an und bestätigt, der Agent führt aus. Nummern = Reihenfolge der Nachrichten.

%%{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 Nutzer
    participant W as Wallet (EUDIPal)
    participant A as Agent
    participant B as Bank (Mandate Service)
    participant H as Händler (Merchant Service)

    rect rgb(243, 244, 246)
    Note over N,A: Flow A — Agent registrieren (einmalig)
    N->>W: Shopping-Agent aktivieren
    W->>A: Identität anlegen
    A->>A: P-256-Key im Secure Enclave (eigener Scope, ohne Biometrie)
    A->>A: Credential: Agent + Betreiber, agent_id aus dem Key
    A-->>W: Name, Betreiber, Credential-Hash
    Note over A: Phase 2: Credential kommt EBW-signiert vom Betreiber
    end

    rect rgb(255, 240, 242)
    Note over N,B: Flow B — ein Mandat, eine SCA
    N->>A: „Kauf mir Milch, Brot, Spülmittel"
    A->>B: POST /mandate-requests (Scopes, agent_credential, JWK)
    B->>B: Agent + Anbieter prüfen (Hash, Key)
    B-->>A: mandate_request_id, transaction_data
    A->>W: Consent anzeigen
    W->>N: Mandat anzeigen (Grenzen, Händler, Warengruppen)
    N->>W: Face ID — SCA mit Mandatstext
    W->>W: Hash über die angezeigten transaction_data
    W->>B: POST /mandates/authorize (hash, amr)
    B-->>W: Mandat aktiv (Spiegel)
    end

    rect rgb(243, 244, 246)
    Note over A,H: Flow C — Einkauf ohne Rückfrage
    A->>H: GET /catalog?q=…
    H-->>A: Artikel, Preise, Warengruppen
    A->>H: POST /attest (Warenkorb, functional_scope, agent)
    H->>H: Fach-Scope prüfen
    H-->>A: signierte Attestation
    A->>A: Signatur prüfen, Zahlungsauftrag signieren (agent_proof)
    A->>B: POST /payments (Hashes, Betrag, agent_proof)
    B->>B: Payment-Scope prüfen (Limits, Händler, Gültigkeit, Signatur)
    B-->>A: Beleg — SCT Inst, Rest im Mandat
    A-->>N: Positionen, Summe, Beleg
    end

    rect rgb(255, 240, 242)
    Note over N,H: Flow D — Ablehnung und Widerruf
    alt Händler lehnt ab (z. B. Bier, category_excluded)
        H-->>A: 422 rejected_by: merchant
        A-->>N: „Händler: Alkohol ist im Mandat ausgeschlossen"
    else Bank lehnt ab (z. B. limit_exceeded)
        B-->>A: 422 rejected_by: bank
        A-->>N: „Bank: Betrag über dem Limit je Einkauf"
    end
    N->>B: Widerruf (Wallet, S-App, Dashboard)
    B-->>N: Mandat widerrufen — keine weitere Zahlung
    end

Die vier Parteien

WalletEUDIPal auf dem iPhone: Consent, SCA, Chat mit dem Agenten
Bankhält das Mandat, prüft den Payment-Scope, settelt per SCT Inst (SPAA-DRP)
HändlerKatalog, Fach-Scope, signierte Attestation — sieht keine Bankdaten
Agenteigener Secure-Enclave-Key, eigenes Credential, signiert jeden Zahlungsauftrag

Stand und Ausblick

Der komplette Ablauf läuft end-to-end und live: Ein Nutzer erteilt der Sparkasse einmal per Face ID ein begrenztes Mandat, danach kauft ein KI-Agent im Chat eigenständig beim Kiezmarkt ein; die Bank prüft jede Zahlung gegen das Mandat, der Händler prüft die Warengruppen, und jede Ablehnung nennt Partei und Grund. Alle drei Parteien haben einen eigenen Bildschirm, die Schnittstellen sind als Spezifikation und OpenAPI 3.1 dokumentiert. Bank und Händler sind bewusst Mocks mit stabilen Schnittstellen.

HeuteMandat, Einkauf, Ablehnung, Widerruf mit Sparkasse-Vorgaben: Agent und Betreiber weisen sich aus, SPAA Dynamic Recurring Payment, Settlement SCT Inst; eigener Transaction-Data-Typ als Vorarbeit zur PaSO-Konformität
Als NächstesMandatsfreigabe als OpenID4VP im PaSO-Profil (Bank als Verifier), Wallet-Erweiterung, Ende-zu-Ende auf dem Gerät
DanachMehrere Händler, UCP als Händler-Schnittstelle, AP2 für fremde Agenten, Agent-Identität aus der European Business Wallet

Detaillierte Roadmap mit Aufwänden, Entscheidungen und Konformitäts-Checkliste: interner Bereich (Anmeldung).

Dokumente

API-Referenz

Maschinenlesbar für Partner: OpenAPI 3.1, gerendert mit Redoc; die Roh-YAML liegt hier und bei jedem Service unter /openapi.yaml.

Live-Systeme

Öffentlich erreichbar, keine Anmeldung. Alles läuft 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 .

# kompletter Bank-Flow B/C/D mit echtem P-256-Agenten-Key (29 Checks)
uv run --python 3.12 --with 'cryptography==43.0.3' python tools/mock-bank/smoke_test.py https://bank.s-you.me

In der App (Build ≥ 751): Chat → „Kauf mir 2 Milch, Brot und Spülmittel“ → Freigabe-Sheet → Face ID → Beleg. „Und ein Bier dazu“ → der Händler lehnt ab, der Agent sagt wer und warum. „Was hat der Agent gekauft?“ → Log der Bank. „Widerrufen“ → danach lehnt die Bank jede Zahlung ab.