Credentials — field reference

Status: 23 October 2026, contract v3. Every credential in the Scoped Mandate PoC, field by field: what each field means, who sets it, who checks it. The field tables on this page are machine-checked: the bank smokes compare them with credentials that bank and operator actually issue, and fail when a field is missing here or documented but no longer issued (see Keeping this page current).

Overview

Credential Type (vct) Issuer Bound to (cnf.jwk) Issued via Presented to
Konto Karte urn:digitallabor:sparkasse:sca:1 the bank, https://bank.s-you.me the customer's wallet key (Secure Enclave, Face ID) OpenID4VCI authorization code after online-banking login the bank (mandate approval, PaSO) and the operator (agent onboarding)
Agent credential urn:digitallabor:agent:1 the operator, https://operator.s-you.me the agent key (Secure Enclave, no biometry) OpenID4VCI pre-authorized code after the customer identified with the Konto Karte the bank (mandate request) and the merchant (purchase)
Mandate credential urn:digitallabor:sparkasse:mandate:1 the bank, https://bank.s-you.me the agent key — the same key as the agent credential OpenID4VCI pre-authorized code from the approved PaSO session the merchant (purchase)

Two further signed objects belong to the picture but are not credentials: the checkout part, which the mandate credential commits to by hash, and the status list, through which issuers revoke. How a holder proves possession of its key when a credential is issued is in proof JWT. Since 2026-10-25 the bank also signs every executed payment: the payment confirmation is the token with which the agent completes a UCP checkout.

Common format

All three credentials are SD-JWT VCs (dc+sd-jwt): <issuer JWT>~<disclosure>~…~ and, when presented, a key-binding JWT appended at the end.

The header is the same for all three credentials:

Field Presence Example Meaning
alg always ES256 Signature algorithm of the issuer JWT.
typ always dc+sd-jwt Media type: an SD-JWT VC.
kid always issuer-1 Key id of the issuer's signing key.
x5c always [leaf, root] Certificate chain, leaf first. Verifiers check the chain to the issuer's root and the issuer signature with the leaf key; the bank also requires the leaf's DNS name to match the host of iss for foreign issuers.

1. Konto Karte

The customer's bank card in the wallet: identity and login at the bank. With it the customer approves mandates (strong customer authentication in the PaSO profile) and identifies to the agent's operator.

Type urn:digitallabor:sparkasse:sca:1, configuration sparkasse_sca
Issued by the bank after login in online banking (OpenID4VCI authorization code with PAR and PKCE)
Lifetime 365 days (CREDENTIAL_TTL_DAYS)
Revocation bank status list; bank dashboard → Credentials → widerrufen
Card in the wallet "Konto Karte", issuer line "Sparkasse Agentenstadt", red

Payload

Field Presence Example Meaning Checked by
iss always https://bank.s-you.me Issuer. bank, operator: on the trust list, x5c leaf bound to this host
vct always urn:digitallabor:sparkasse:sca:1 Credential type. bank, operator: must be an accepted SCA type
iat always 1792704125 Issued at. —
nbf always 1792704065 Valid from; iat minus 60 s against clock skew. —
exp always 1824240125 Valid until. bank, operator
issuing_bank always Sparkasse Agentenstadt Display name of the bank (display only; trust comes from iss). —
cnf.jwk always {kty: EC, crv: P-256, x, y} The customer's wallet key. Only this key can sign the key-binding JWT of a presentation. bank, operator: key binding
status.status_list.uri always https://bank.s-you.me/issuer/status/1 Status list the credential is registered in. bank, operator
status.status_list.idx always 193 Bit index in that list; 1 = revoked. bank, operator
_sd always ["3A5K…", …] Digests of the selectively disclosable claims below. verifiers match each presented disclosure
_sd_alg always sha-256 Digest algorithm for _sd. —

Selectively disclosable

Field Presence Example Meaning Who asks for it
given_name always Yuki First name. operator (onboarding: who onboards the agent)
family_name always Tanaka Last name. operator
iban_masked always DE89 **** **** **** 3000 Account, masked. —
customer_id always cust_yuki_0001 The bank's customer reference. bank (mandate approval: becomes principal of the mandate credential); operator
sca_level always sca Assurance of the login behind the card. —

Presentation

The wallet appends a key-binding JWT (typ: kb+jwt), signed with the key in cnf.jwk, after Face ID.

To Required claims in the key-binding JWT
the bank, mandate approval (PaSO, §1.14) see the table below; the bank publishes the required set as kb_jwt_claims_required at /paso/.well-known
the operator, agent onboarding (§1.16) nonce, aud, iat, sd_hash

Key-binding JWT to the bank for the mandate approval (PaSO Core §6.1 plus key binding):

Field Presence Example Meaning
nonce always n-0S6_WzA2Mj Nonce of the bank's request (JAR); single use.
aud always x509_san_dns:bank.s-you.me The bank's client id.
iat always 1792704137 Signed at; at most 5 minutes old.
sd_hash always Kb3… Hash over the presented SD-JWT without the key-binding JWT.
transaction_data_hashes always ["q9X…"] SHA-256 of the transmitted transaction_data entry — the mandate the customer saw (payment part and hash C). Dynamic linking.
transaction_data_hashes_alg optional sha-256 Algorithm for the line above.
jti always 6a1f… Unique id of this approval; replay protection.
response_mode always direct_post.jwt How the wallet answered (encrypted).
display_locale always de-DE Language the mandate was shown in.
amr always ["hwk", "bio_strong", "face"] Authentication methods; becomes sca.amr of the mandate credential.
transaction_data_hash always q9X… PaSO form of the hash above (single value).
transaction_data_hash_alg always sha-256 Its algorithm.
request_integrity always sha256-… Hash of the request (JAR) the wallet displayed; proves the wallet saw the bank's exact request.
wallet_instance_version always ios:793:2.6.0 Wallet build.
metadata_integrity optional sha256-… Hash of the verifier metadata, if the wallet sends it.

2. Agent credential

The shopping agent's identity, issued by its operator. It ties the agent key to a legal person that is accountable for the agent. In the target picture it comes from the operator's European Business Wallet.

Type urn:digitallabor:agent:1, configuration agent_identity
Issued by the operator after the customer presented the Konto Karte there (OpenID4VCI pre-authorized code, proof signed by the agent key)
Lifetime 365 days
Revocation operator status list; operator portal → widerrufen
In the app the green agent card "EUDIPal Einkäufer"

Payload

Field Presence Example Meaning Checked by
iss always https://operator.s-you.me Issuer: the operator. bank (on AGENT_TRUSTED_ISSUERS), merchant (chain to the operator root)
vct always urn:digitallabor:agent:1 Credential type. bank, merchant
iat always 1792704124 Issued at. —
nbf always 1792704064 Valid from. —
exp always 1824240124 Valid until. bank, merchant
agent_id always agt_0db3f2b42011cdd0 Agent id: agt_ + first 16 hex of SHA-256 over the agent's public key (SEC1, 0x04‖X‖Y). Derived from the key, so identity and key cannot drift apart. bank: equals the short form in the mandate request
issuer_role always operator Role of the issuer. —
cnf.jwk always {kty: EC, crv: P-256, x, y} The agent key. The same key appears in the mandate credential. bank: equals agent_public_key_jwk; merchant: equals the mandate credential's cnf
status.status_list.uri always https://operator.s-you.me/issuer/status/1 Operator status list. bank, merchant
status.status_list.idx always 88 Bit index. bank, merchant
_sd always ["Xnb3…", …] Digests of the disclosable claims below. —
_sd_alg always sha-256 Digest algorithm. —

Selectively disclosable

Field Presence Example Meaning Who asks for it
agent_display_name always EUDIPal Einkäufer Name of the agent. bank, merchant
operator_legal_person always digitallabor.berlin GmbH The operator as a legal person. bank, merchant
operator_register_id always HRB 000000 B (Mock) Register number of the operator. merchant

The operator also records who onboarded the agent (the Konto Karte's name and customer reference). That stays in the operator's register; it is not in the credential.

Presentation

The agent signs the key-binding JWT with the agent key; no Face ID.

To Required claims in the key-binding JWT
the bank, mandate request (agent_presentation, §1.16) nonce (from POST /agent-nonces, single use), aud, iat (max. 5 min old), sd_hash; jti is recorded if present
the merchant, purchase (§2.5) nonce, aud = merchant id, iat, sd_hash, transaction_data_hashes (hash of the basket entry), transaction_data_hashes_alg; jti is recorded if present

3. Mandate credential

The approved mandate itself, issued by the bank to the agent key (contract v3). It carries the payment part in the clear and commits to the checkout part by hash. The bank keeps the mandate; this credential is its portable proof.

Type urn:digitallabor:sparkasse:mandate:1, configuration scoped_mandate
Issued by the bank when the PaSO session of the approval turns authorized: the session result carries a pre-authorized-code offer, the agent redeems it with a proof signed by the agent key. One credential per mandate; the proof key must equal the mandate's agent_public_key_jwk
Lifetime until the mandate's payment_scope.valid_until
Revocation bank status list; set by POST /mandates/{id}/revoke, the SPAA cancel and the dashboard
In the app not a card of its own; shown in the agent card's mandate list ("Mandats-Credential: von bank.s-you.me · Status-Bit …")

Payload

Field Presence Example Meaning Checked by
iss always https://bank.s-you.me Issuer: the bank. merchant: chain to the bank root, iss = the bank
vct always urn:digitallabor:sparkasse:mandate:1 Credential type. merchant
iat always 1792704139 Issued at. —
nbf always 1792704079 Valid from. merchant
exp always 1795296125 Valid until; equals the end of the mandate. merchant
issuing_bank always Sparkasse Agentenstadt Display name of the bank. —
mandate_id always mnd_b5cada6da34943c9 The mandate at the bank. Payment orders reference it. merchant: equals the mandate of the checkout session
cnf.jwk always {kty: EC, crv: P-256, x, y} The agent key, the same as in the agent credential. merchant: equals the agent credential's cnf
status.status_list.uri always https://bank.s-you.me/issuer/status/1 Bank status list. merchant
status.status_list.idx always 198 Bit index; the bank sets it when the mandate is revoked. merchant: bit set → checkout_mandate_revoked
payment_scope.max_per_transaction always 6000 Limit per purchase in cents (60 €). the bank at payment (limit_exceeded)
payment_scope.max_total always 30000 Total limit in cents (300 €). the bank at payment (total_exceeded)
payment_scope.currency always EUR Currency. the bank at payment
payment_scope.valid_from always 2026-10-22T21:21:05Z Start of the mandate. merchant, bank
payment_scope.valid_until always 2026-11-21T21:22:05Z End of the mandate. merchant, bank
payment_scope.merchant_whitelist always [] Named merchants at which the agent may pay. May be empty (since 2026-10-24, like AP2 merchants): then any merchant of a kind in merchant_categories. Mandates from builds ≤ 796: ["merchant_kiezmarkt"]. merchant: if non-empty, itself on the list, else mandate_not_verified; bank at payment (merchant_not_allowed)
payment_scope.merchant_categories always ["5411"] Merchant category codes (ISO 18245; 5411 = grocery stores, 5912 = drugstores). The kind of merchant, not product categories; from the SPAA Dynamic Recurring Payment (creditorCategoryCodes). At least one of merchant_whitelist and merchant_categories is non-empty. the bank at payment, against the MCC in its own merchant directory (merchant_category_not_allowed) — not the merchant
payment_scope.scheme always spaa_drp The mandate is an SPAA Dynamic Recurring Payment. —
payment_scope.rail always sct_inst Every payment settles as SEPA Instant Credit Transfer. —
checkout_hash always sha256:92c20e5c… Hash C — SHA-256 over the canonical JSON of the checkout part (product categories, age rule). The bank only knows this hash; the merchant recomputes it over the checkout part it receives in the clear. merchant: mismatch → checkout_mandate_invalid
sca.amr always ["hwk", "bio_strong", "face"] How the customer approved, from the PaSO key-binding JWT: hardware key, strong biometry, Face ID. —
sca.ts always 2026-10-22T21:22:17Z When the customer approved. —
sca.credential always urn:digitallabor:sparkasse:sca:1 With which credential: the Konto Karte. —
_sd always ["7yDs…"] Digests of the disclosable claims below. —
_sd_alg always sha-256 Digest algorithm. —

Selectively disclosable

Field Presence Example Meaning Who asks for it
agent_id always agt_0db3f2b42011cdd0 The agent the mandate belongs to. nobody; the merchant binds via cnf
principal optional cust_yuki_0001 The bank's customer reference — only if the customer disclosed customer_id of the Konto Karte during the approval; never taken from the mandate request. nobody; the agent presents the mandate credential to the merchant without disclosures

Presentation

Only to the merchant, by the agent, together with the agent credential and the checkout part in the clear (§2.5): <issuer JWT>~<key-binding JWT> — no disclosures. Key-binding JWT signed with the agent key: nonce, aud = merchant id, iat, sd_hash (over exactly the presented <issuer JWT>~), transaction_data_hashes (the basket entry), transaction_data_hashes_alg; jti is recorded if present.

4. Checkout part (not a credential)

The product rules of the mandate. Built by the wallet, shown to the customer next to the payment part, signed by nobody: its hash went into the approved transaction_data (signed by the Konto Karte key) and into the mandate credential (signed by the bank). Held by wallet and agent, sent to the merchant in the clear at every purchase, kept in the merchant's log. The bank never sees it.

Field Presence Example Meaning
checkout_mandate_id always cm_5a7d… Id of this checkout part.
mandate_request_id always mrq_pending Placeholder: hash C is computed before the bank assigns the request id.
functional_scope.category_scheme always google_product_taxonomy:2021-09-21 Which category list the IDs below come from: the Google Product Taxonomy, version 2021-09-21. Missing in legacy checkout parts (mock words).
functional_scope.allowed_categories always ["422", "413", "630"] Google Product Taxonomy IDs the agent may buy from — here Food Items, Beverages, Household Supplies. A category includes all its subcategories. Empty = everything not excluded.
functional_scope.excluded_categories always ["499676", "435", "222"] IDs that are never allowed — here Alcoholic Beverages, Tobacco Products, Electronics. Exclusion wins over allowance.
functional_scope.age_restricted_allowed always false Whether age-restricted items may be bought.
valid_from always 2026-10-22T21:21:05Z Start, whole seconds.
valid_until always 2026-11-21T21:21:05Z End, whole seconds.

Hash C = sha256: + hex SHA-256 over the canonical JSON of exactly these fields (sorted keys, no whitespace, / not escaped). Categories are Google Product Taxonomy IDs (Interface Specification §1.10.2: matching rule, legacy words). Legacy v2 checkout parts additionally carried principal_key, signature and a credential form urn:digitallabor:checkout_mandate:1 signed by the customer; the merchant still accepts them from app builds ≤ 792.

5. Status list token

How issuers revoke: one bit per credential, served as a signed token at /issuer/status/1 (bank and operator each have their own). Verifiers fetch it and check the bit at status.status_list.idx.

Header: alg ES256, typ statuslist+jwt, kid, x5c (bound to the host of the list).

Field Presence Example Meaning
iss always https://bank.s-you.me Issuer of the list.
sub always https://bank.s-you.me/issuer/status/1 URI of the list; must equal status.status_list.uri of the credential.
iat always 1792737549 Created at.
ttl always 300 Seconds a verifier may cache the list.
status_list.bits always 1 Bits per entry.
status_list.lst always eNqbxMjF… The bit string, zlib-compressed, base64url. Grows with the number of issued credentials.

6. Proof JWT (issuance)

When a wallet or the agent redeems an offer, it proves possession of the key the credential will be bound to. Header: alg ES256, typ openid4vci-proof+jwt, jwk = the holder's public key (becomes cnf.jwk). Payload: iss (client id: wallet-dev for the wallet, eudipal-agent for the agent), aud = the issuer URL, iat, nonce from /issuer/nonce (single use). For the mandate credential the bank additionally requires jwk to be the mandate's agent key.

7. Payment confirmation (UCP token)

Not a credential: a compact JWS the bank returns with every executed payment (POST /payments → confirmation). The agent hands it to the merchant as payment.instruments[0].credential.token of the UCP payment handler de.digitallabor.bank_push; the merchant verifies it against the bank root without calling the bank (Interface Specification §2.6).

Field Presence Example Meaning
header alg / typ / kid always ES256 / payment-confirmation+jwt / issuer-1 same issuer key as the bank's credentials
header x5c always [issuer cert, root] chain to https://bank.s-you.me/issuer/root.pem
iss always https://bank.s-you.me the bank
typ always payment_confirmation —
payment_id, mandate_id, merchant_id, amount, currency always pay_…, mnd_…, merchant_kiezmarkt, 258, EUR the executed payment; the merchant compares merchant, mandate and amount with its checkout
attestation_id, checkout_id always (null for non-UCP attestations) att_…, chk_… binds the payment to the merchant's attestation and UCP checkout
end_to_end_id, instrument, executed_at, status always E2E…, sct_inst, 2026-10-25T08:05:34Z, executed settlement reference

Keeping this page current

The tables between the markers <!-- fields:… --> and <!-- /fields --> are read by tools/mock-bank/credential_docs.py. The bank smokes call it with the credentials they actually obtain:

Smoke Checks
smoke_issuer.py Konto Karte: header, payload, disclosures; status list payload
smoke_agent.py agent credential: header, payload, disclosures
smoke_mandate_vc.py mandate credential: header, payload, disclosures
smoke_paso.py PaSO key-binding claims (/paso/.well-known) against the Konto Karte presentation table

A smoke fails when an issued field is not documented here (a nested field is covered by a documented parent, e.g. cnf.jwk covers cnf.jwk.x), or when a field documented as always is missing in the issued credential. Changing a credential therefore means changing this page in the same commit.