EUDI API

The EUDI API creates the wallet instance key and attestation, the credential keys with a key attestation, and the key-binding signatures of an EU Digital Identity wallet. See EUDI Wallet Flows for how the calls fit together and API General Principles for the shared conventions.

Target design; the endpoints are not implemented yet.

All four endpoints share these rules:

  • Caller: the Wallet Integration Server, forwarding the wallet’s request unchanged.
  • Path parameter: unitId (UnitId).
  • Encryption: the request and the success response are encrypted (see the envelope).
  • Authorization: a vault unlock and device signatures. The plaintext is a signed request, signed by every device key of the unit, whose aud is the path with the unit id filled in. The request parameters documented below are its request claim.

Every check that needs no HSM runs before the HSM is used: the signed request, the schema, the unit state, the activation status, and the checks of the proof-of-possession (PoP) JWTs. The samples show the claims of the signed request and the decrypted response.

Error Handling

Generic error codes (validation, authentication, HSM, PowerAuth Server, internal) are listed once in Possible Error Codes. Each endpoint below shows its error responses as Response 4xx and Response 5xx tabs, with a sample problem body and the possible codes.

Wallet Instance Services

post /v1/eudi/units/{unitId}/wallet-instance-keys Create the WIA Key

Creates one WALLET_INSTANCE key: the key that a wallet instance attestation (WIA) names in cnf. Each call creates a new key.

Request

Request Headers
Content-Type: application/json
X-PowerAuth-Encryption: PowerAuth version="4.0", application_key="znZ5XajbFZAq/jv2HCJLoQ==", activation_id="2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4"

The activation_id must equal the path unitId; see the envelope.

Path Params
Param Type Description
unitId* UnitId The unit id.
Request Body

The request claim is exactly the empty object {}; any member is 400.

The Claims tab shows the claims of the signed request; the Envelope tab shows the request on the wire, the signed request encrypted in the envelope.

{
  "aud": "/v1/eudi/units/2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4/wallet-instance-keys",
  "sub": "2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4",
  "iat": 1793271600,
  "jti": "q3Vb8Kx0TfW2nLc7RgJ1sA",
  "request": {}
}
POST /v1/eudi/units/2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4/wallet-instance-keys HTTP/1.1
Content-Type: application/json
X-PowerAuth-Encryption: PowerAuth version="4.0", application_key="znZ5XajbFZAq/jv2HCJLoQ==", activation_id="2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4"

{"temporaryKeyId":"e8cd1538-ef37-4c8e-b745-0e8e9f326ecc","encryptedData":"GbydIAxxoU8qUFHUhpZQa1BzAJyK2/myJsLJtpJWjz8…","nonce":"CVBa2DGm1w1gQvM0h6WUXHG8S+cm0tCd","timestamp":1793271600123}

Response

The decrypted response; on the wire the body is an EncryptedResponse (see the envelope).

{
  "publicJwk": {
    "kty": "EC",
    "crv": "P-256",
    "x": "ZwvVwPh77m4omLkSfP6hXFxcw4MS4ylNbNbiWMAKN_s",
    "y": "FwIsmFW2stvG5CN5OVQ42KH3gvAsx-Fv2XnsB_F6VU4"
  },
  "wrappedKey": "eyJ0eXAiOiJzay13cmFwcGVkLWtleStqd2UiLCJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwidmVyIjoiMSIsImtpZCI6IjEiLCJpc3MiOiJzZWN1cmUta2V5c3RvcmUifQ..…"
}
Description of Response Params
Attribute Type Description
publicJwk EcPublicJwk The public key of the WIA key; it becomes the WIA’s cnf.
wrappedKey WrappedKey The WIA key, purpose WALLET_INSTANCE, sealed to the unit’s device keys.

The request is invalid or refers to an unknown object.

{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "code": "invalid_request"
}

Possible error codes:

  • invalid_request - The request fails a schema, pattern, encoding or transport rule (see Common Conventions).
  • unit_not_found - The unit does not exist.

The caller is not authorized for this request.

{
  "type": "about:blank",
  "title": "Forbidden",
  "status": 403,
  "code": "invalid_user_authorization"
}

Possible error codes:

  • invalid_user_authorization - The encryption header, the signed request or the activation status refuses the request.

The request conflicts with the state of the unit, the key or the wrapped key.

{
  "type": "about:blank",
  "title": "Conflict",
  "status": 409,
  "code": "unit_revoked"
}

Possible error codes:

  • unit_revoked - The unit is revoked.

An internal error occurred.

{
  "type": "about:blank",
  "title": "Internal Server Error",
  "status": 500,
  "code": "internal_error"
}

Possible error codes:

  • internal_error - A token or key configuration fault, or an unexpected error; no detail.

A dependency is unavailable; retry after the number of seconds in Retry-After.

{
  "type": "about:blank",
  "title": "Service Unavailable",
  "status": 503,
  "code": "hsm_unavailable"
}

Possible error codes:

  • hsm_unavailable - The HSM is unavailable.
  • authorization_unavailable - PowerAuth Server is unreachable, timed out, or returned an error other than a refusal.

post /v1/eudi/units/{unitId}/wallet-instance-attestations Issue a WIA and Sign Its Proofs of Possession

Checks the unsigned client-attestation PoP JWTs, signs them with the WIA key and verifies the signatures against publicJwk. Only then does it issue a WIA (oauth-client-attestation+jwt, signed with sign-eudi-ecdsa-wia). The call succeeds completely or not at all.

Request

Request Headers
Content-Type: application/json
X-PowerAuth-Encryption: PowerAuth version="4.0", application_key="znZ5XajbFZAq/jv2HCJLoQ==", activation_id="2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4"

The activation_id must equal the path unitId; see the envelope.

Path Params
Param Type Description
unitId* UnitId The unit id.
Request Body

The Claims tab shows the claims of the signed request (the members below are its request claim); the Envelope tab shows the request on the wire, the signed request encrypted in the envelope.

{
  "aud": "/v1/eudi/units/2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4/wallet-instance-attestations",
  "sub": "2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4",
  "iat": 1793271600,
  "jti": "Hn4cR1xVt9QpZ0eKw2LmYa",
  "request": {
    "publicJwk": {
      "kty": "EC",
      "crv": "P-256",
      "x": "ZwvVwPh77m4omLkSfP6hXFxcw4MS4ylNbNbiWMAKN_s",
      "y": "FwIsmFW2stvG5CN5OVQ42KH3gvAsx-Fv2XnsB_F6VU4"
    },
    "wrappedKey": "eyJ0eXAiOiJzay13cmFwcGVkLWtleStqd2UiLCJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwidmVyIjoiMSIsImtpZCI6IjEiLCJpc3MiOiJzZWN1cmUta2V5c3RvcmUifQ..…",
    "walletVersion": "2.4.1",
    "popJwts": ["eyJ0eXAi….eyJpc3Mi…", "eyJ0eXAi….eyJpc3Mi…"]
  }
}
POST /v1/eudi/units/2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4/wallet-instance-attestations HTTP/1.1
Content-Type: application/json
X-PowerAuth-Encryption: PowerAuth version="4.0", application_key="znZ5XajbFZAq/jv2HCJLoQ==", activation_id="2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4"

{"temporaryKeyId":"e8cd1538-ef37-4c8e-b745-0e8e9f326ecc","encryptedData":"GbydIAxxoU8qUFHUhpZQa1BzAJyK2/myJsLJtpJWjz8…","nonce":"CVBa2DGm1w1gQvM0h6WUXHG8S+cm0tCd","timestamp":1793271600123}
Request Params
Attribute Type Description
publicJwk* EcPublicJwk Public key of the WIA key as returned by wallet-instance-keys; becomes the WIA’s cnf, rebuilt from the validated coordinates.
wrappedKey* WrappedKey Purpose WALLET_INSTANCE of this unit; the wrapped WIA key that signs the PoPs.
walletVersion* String The app version, MAJOR.MINOR.PATCH with 1 to 4 digits each. Becomes wallet_version; nobody verifies it.
popJwts* String[] 1 or 2 unsigned PoP JWTs, BASE64URL(header).BASE64URL(payload), each 3 to 2048 characters. One per call to the authorization server (PAR, token request).

Each PoP is checked:

  • Encoding: both segments are canonical base64url without padding, and both decode to strict UTF-8 JSON without duplicate members.
  • Header: typ is oauth-client-attestation-pop+jwt, alg is ES256, and there is no crit.
  • Payload: iss equals the configured WIA client id; aud is a string of 1 to 512 characters; jti is a string of 1 to 256 characters; iat is an integer.
  • Time: the keystore does not check the PoP’s time; the authorization server does.

Other members, such as the authorization server’s challenge, are allowed.

Response

The decrypted response; on the wire the body is an EncryptedResponse (see the envelope).

{
  "attestation": "eyJ0eXAiOiJvYXV0aC1jbGllbnQtYXR0ZXN0YXRpb24rand0Ii….eyJzdWIi….<signature>",
  "pops": ["eyJ0eXAi…(header).eyJhdWQi…(payload).G0HQt7YD…", "eyJ0eXAi….eyJhdWQi….k88XMXa-…"]
}
Description of Response Params
Attribute Type Description
attestation String The WIA: a compact JWT (typ oauth-client-attestation+jwt, ES256, x5c), valid 10 minutes by default.
pops String[] The signed PoP JWTs (header.payload.signature), one per popJwts entry, in request order.

WIA payload members, in order:

  • iss, the wallet provider (keystore.eudi.wia.issuer, required): OAuth Attestation-Based Client Authentication draft 07 requires it, and later drafts and TS3 allow it;
  • sub, cnf;
  • iat, rounded down to the minute, and exp;
  • wallet_name, wallet_version, wallet_solution_certification_information;
  • client_status.

The request is invalid or refers to an unknown object.

{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "code": "invalid_request"
}

Possible error codes:

  • invalid_request - The request fails a schema, pattern, encoding or transport rule (see Common Conventions).
  • unit_not_found - The unit does not exist.

The caller is not authorized for this request.

{
  "type": "about:blank",
  "title": "Forbidden",
  "status": 403,
  "code": "invalid_user_authorization"
}

Possible error codes:

  • invalid_user_authorization - The encryption header, the signed request or the activation status refuses the request.

The request conflicts with the state of the unit, the key or the wrapped key.

{
  "type": "about:blank",
  "title": "Conflict",
  "status": 409,
  "code": "unit_revoked"
}

Possible error codes:

  • unit_revoked - The unit is revoked.
  • wrapped_key_invalid - A wrapped key is tampered, of another unit or purpose, malformed inside, or does not sign for the WIA key’s publicJwk. One byte-identical body for every cause.

An internal error occurred.

{
  "type": "about:blank",
  "title": "Internal Server Error",
  "status": 500,
  "code": "internal_error"
}

Possible error codes:

  • internal_error - A token or key configuration fault, or an unexpected error; no detail.

A dependency is unavailable; retry after the number of seconds in Retry-After.

{
  "type": "about:blank",
  "title": "Service Unavailable",
  "status": 503,
  "code": "hsm_unavailable"
}

Possible error codes:

  • hsm_unavailable - The HSM is unavailable.
  • authorization_unavailable - PowerAuth Server is unreachable, timed out, or returned an error other than a refusal.

Credential Services

post /v1/eudi/units/{unitId}/credential-keys Create Credential Keys with a Key Attestation

Creates keyCount CREDENTIAL keys and one key attestation (key-attestation+jwt, signed with sign-eudi-ecdsa-ka) over them, bound to the issuer’s c_nonce. On request it also builds the OpenID4VCI proof JWT (proof type jwt), so no further call is needed. Keys, attestation and proof are atomic.

The key attestation’s key_storage and user_authentication members state how the keys are protected and how the user is authenticated, as certified for the wallet provider.

Request

Request Headers
Content-Type: application/json
X-PowerAuth-Encryption: PowerAuth version="4.0", application_key="znZ5XajbFZAq/jv2HCJLoQ==", activation_id="2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4"

The activation_id must equal the path unitId; see the envelope.

Path Params
Param Type Description
unitId* UnitId The unit id.
Request Body

The Claims tab shows the claims of the signed request (the members below are its request claim); the Envelope tab shows the request on the wire, the signed request encrypted in the envelope.

{
  "aud": "/v1/eudi/units/2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4/credential-keys",
  "sub": "2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4",
  "iat": 1793271600,
  "jti": "b7TzQ0aPq1Wm4sXeN8yKcg",
  "request": {
    "keyCount": 2,
    "cNonce": "tZignsnFbp",
    "proof": {"aud": "https://issuer.example.com", "iss": "https://wallet.example.com"}
  }
}
POST /v1/eudi/units/2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4/credential-keys HTTP/1.1
Content-Type: application/json
X-PowerAuth-Encryption: PowerAuth version="4.0", application_key="znZ5XajbFZAq/jv2HCJLoQ==", activation_id="2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4"

{"temporaryKeyId":"e8cd1538-ef37-4c8e-b745-0e8e9f326ecc","encryptedData":"GbydIAxxoU8qUFHUhpZQa1BzAJyK2/myJsLJtpJWjz8…","nonce":"CVBa2DGm1w1gQvM0h6WUXHG8S+cm0tCd","timestamp":1793271600123}
Request Params
Attribute Type Description
keyCount* Integer 1 to 16 and at most keystore.keys.max-count (default 16), otherwise 400 (detail keyCount: Max).
cNonce* String 1 to 512 printable ASCII characters without space, " and \; the issuer’s c_nonce, embedded verbatim as the nonce of the attestation and of the proof.
proof Object Asks for the proof JWT. Members aud and iss, see below.

The proof object has exactly these members:

  • aud (required): the Credential Issuer identifier, an absolute https URL with a host and no fragment, 1 to 512 characters;
  • iss (optional): the wallet’s client_id, 1 to 512 printable ASCII characters.

Response

The decrypted response; on the wire the body is an EncryptedResponse (see the envelope).

{
  "keys": [
    {
      "publicJwk": {
        "kty": "EC",
        "crv": "P-256",
        "x": "qGo2fJa7Oo_n5CI96iVoPx-0KZVyAZ0d6QdUPBeBvoo",
        "y": "iFDMVPNKcuzOIT8tp5wN8hVipiKUgtoSjL7AyREj_DE"
      },
      "wrappedKey": "eyJ0eXAiOiJzay13cmFwcGVkLWtleStqd2UiLCJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwidmVyIjoiMSIsImtpZCI6IjEiLCJpc3MiOiJzZWN1cmUta2V5c3RvcmUifQ..…"
    }
  ],
  "keyAttestation": "eyJ0eXAiOiJrZXktYXR0ZXN0YXRpb24rand0Ii….eyJpYXQiOjE3OTMyNzE2MDAs….<signature>",
  "proof": "eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVTMjU2Ii….eyJhdWQiOiJodHRwczovL2lzc3Vlci5leGFtcGxlLmNvbSIs….<signature>"
}
Description of Response Params
Attribute Type Description
keys Object[] 1 to 16 {publicJwk, wrappedKey} objects, in the order of attested_keys.
keyAttestation String The KA: a compact JWT (typ key-attestation+jwt, ES256, x5c), valid 10 minutes by default.
proof String Only when requested: the compact proof JWT, signed by key 0.

KA payload members, in order: iat, exp, nonce (the cNonce), attested_keys, key_storage, user_authentication, the certification URL, and the status entry key_storage_status.

The proof JWT has:

  • header: typ openid4vci-proof+jwt, alg ES256, jwk (equal to attested_keys[0]) and key_attestation (this response’s keyAttestation);
  • payload: aud, iss (if given), nonce (the cNonce) and iat (the KA’s iat).

Key 0 signs the proof in the HSM before it is wrapped, and the keystore verifies the signature before it responds. One proof covers the whole batch: it carries the key attestation in its header, and OpenID4VCI 1.0 (Appendix F.1) has the issuer issue a credential for each key in the attestation’s attested_keys.

The request is invalid or refers to an unknown object.

{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "code": "invalid_request"
}

Possible error codes:

  • invalid_request - The request fails a schema, pattern, encoding or transport rule (see Common Conventions).
  • unit_not_found - The unit does not exist.

The caller is not authorized for this request.

{
  "type": "about:blank",
  "title": "Forbidden",
  "status": 403,
  "code": "invalid_user_authorization"
}

Possible error codes:

  • invalid_user_authorization - The encryption header, the signed request or the activation status refuses the request.

The request conflicts with the state of the unit, the key or the wrapped key.

{
  "type": "about:blank",
  "title": "Conflict",
  "status": 409,
  "code": "unit_revoked"
}

Possible error codes:

  • unit_revoked - The unit is revoked.

An internal error occurred.

{
  "type": "about:blank",
  "title": "Internal Server Error",
  "status": 500,
  "code": "internal_error"
}

Possible error codes:

  • internal_error - A token or key configuration fault, or an unexpected error; no detail.

A dependency is unavailable; retry after the number of seconds in Retry-After.

{
  "type": "about:blank",
  "title": "Service Unavailable",
  "status": 503,
  "code": "hsm_unavailable"
}

Possible error codes:

  • hsm_unavailable - The HSM is unavailable.
  • authorization_unavailable - PowerAuth Server is unreachable, timed out, or returned an error other than a refusal.

post /v1/eudi/units/{unitId}/key-binding-signatures Sign Key-Binding JWTs

Signs 1 to 16 SHA-256 hashes the wallet computed (key-binding JWTs, mdoc device authentication) with CREDENTIAL keys. All or nothing: every wrapped key is opened and checked before any is unwrapped.

Request

Request Headers
Content-Type: application/json
X-PowerAuth-Encryption: PowerAuth version="4.0", application_key="znZ5XajbFZAq/jv2HCJLoQ==", activation_id="2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4"

The activation_id must equal the path unitId; see the envelope.

Path Params
Param Type Description
unitId* UnitId The unit id.
Request Body

The Claims tab shows the claims of the signed request (the members below are its request claim); the Envelope tab shows the request on the wire, the signed request encrypted in the envelope.

{
  "aud": "/v1/eudi/units/2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4/key-binding-signatures",
  "sub": "2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4",
  "iat": 1793271600,
  "jti": "Zx9fLm2Rk0VbT4qWc6NsPe",
  "request": {
    "items": [
      {"wrappedKey": "eyJ0eXAiOiJzay13cmFwcGVkLWtleStqd2UiLCJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwidmVyIjoiMSIsImtpZCI6IjEiLCJpc3MiOiJzZWN1cmUta2V5c3RvcmUifQ..…", "signingInput": "mpDFaLPskYQgCKSSzGyIvcXnasdgqBJctGdctEpUXL8"}
    ]
  }
}
POST /v1/eudi/units/2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4/key-binding-signatures HTTP/1.1
Content-Type: application/json
X-PowerAuth-Encryption: PowerAuth version="4.0", application_key="znZ5XajbFZAq/jv2HCJLoQ==", activation_id="2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4"

{"temporaryKeyId":"e8cd1538-ef37-4c8e-b745-0e8e9f326ecc","encryptedData":"GbydIAxxoU8qUFHUhpZQa1BzAJyK2/myJsLJtpJWjz8…","nonce":"CVBa2DGm1w1gQvM0h6WUXHG8S+cm0tCd","timestamp":1793271600123}
Request Params
Attribute Type Description
items* Object[] 1 to 16 items, the signatures to make.
items[].wrappedKey* WrappedKey Purpose CREDENTIAL of this unit; the credential key.
items[].signingInput* Sha256Digest The SHA-256 digest of the KB-JWT’s JWS signing input.

Items may contain only wrappedKey and signingInput; any other member is 400.

Response

The decrypted response; on the wire the body is an EncryptedResponse (see the envelope).

{
  "signatures": ["eUQ-4L-9nH9BkEMb_4KUatehhLGbSdxr9h9xfbZpSNqAiosLoPcgsInse2kA0dPMCWVYM4gD1mK9A8xiMprNrw"]
}
Description of Response Params
Attribute Type Description
signatures Signature[] 1 to 16 values, one ES256 R‖S signature per item, in request order.

The request is invalid or refers to an unknown object.

{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "code": "invalid_request"
}

Possible error codes:

  • invalid_request - The request fails a schema, pattern, encoding or transport rule (see Common Conventions).
  • unit_not_found - The unit does not exist.

The caller is not authorized for this request.

{
  "type": "about:blank",
  "title": "Forbidden",
  "status": 403,
  "code": "invalid_user_authorization"
}

Possible error codes:

  • invalid_user_authorization - The encryption header, the signed request or the activation status refuses the request.

The request conflicts with the state of the unit, the key or the wrapped key.

{
  "type": "about:blank",
  "title": "Conflict",
  "status": 409,
  "code": "unit_revoked"
}

Possible error codes:

  • unit_revoked - The unit is revoked.
  • wrapped_key_invalid - A wrapped key is tampered, of another unit, purpose, owner, key type, format version or generation, or malformed inside; in a later version also too old (maximum age). One byte-identical body for every cause.

An internal error occurred.

{
  "type": "about:blank",
  "title": "Internal Server Error",
  "status": 500,
  "code": "internal_error"
}

Possible error codes:

  • internal_error - A token or key configuration fault, or an unexpected error; no detail.

A dependency is unavailable; retry after the number of seconds in Retry-After.

{
  "type": "about:blank",
  "title": "Service Unavailable",
  "status": 503,
  "code": "hsm_unavailable"
}

Possible error codes:

  • hsm_unavailable - The HSM is unavailable.
  • authorization_unavailable - PowerAuth Server is unreachable, timed out, or returned an error other than a refusal.
Last updated on Nov 11, 2026 (21:37) Edit on Github Send Feedback

develop

Secure Keystore