> For the complete documentation index, see [llms.txt](https://docs.toolkit.equs.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.toolkit.equs.ai/docs/presentation/configuration/configuration-eudi-ecosystem.md).

# Configuration: EUDI Ecosystem

What changes when your Verifier must work with European Digital Identity Wallet

The **European Digital Identity Wallet (EUDI Wallet)** is the wallet every EU Member State must offer its citizens under the eIDAS 2 regulation. It speaks the same OID4VP that any other wallet here does, with one difference that matters to you: it won't answer a request from an anonymous Verifier. It wants to know, from certificates rather than from your word for it, who is asking and whether they're allowed to ask.

Three things change, and everything else about verification stays as it is:

* **Your Verifier is identified by an X.509 certificate.** That certificate belongs to the deployment, and **EQUS obtains and installs it** - nothing for you to do.
* **You need a second certificate, from your national Registrar,** saying what you're authorized to ask for. This one is yours to get, and most of this page is about it.
* **Your presentation requests name the purpose you're asking under.** One extra field, `intendedUseId`, on the call you already make.

Trusting the credential's Issuer and verifying the response work exactly as they do today.

If the wallets you deal with aren't EUDI-aligned, none of this applies and you can skip the page.

## Switch your Verifier to x509

`clientIdPrefix` decides how wallets identify you. Set it to `x509_hash` instead of the default `did_web`, either when you create the Verifier or later with an update.

**Use `x509_hash` unless a wallet specifically demands otherwise.** The alternative, `x509_san_dns`, binds your Client Identifier to the deployment's own hostname.

You never supply the certificate yourself - EQUS uploads it for the whole deployment.

{% hint style="warning" %}
**An access certificate has to be active in your environment before you start.** Until it is, creating an x509 Verifier succeeds, but every presentation request fails.
{% endhint %}

Only `name` is required. Fields you leave out keep their current values rather than being reset, so `clientIdPrefix` is the only one you need here.

The call below also carries **`trustedRootCerts`**, which is a different concern - not how wallets identify you, but which Issuers you're willing to believe. Leave it alone for now; [Trusting the issuers you verify](#trusting-the-issuers-you-verify) covers what belongs there.

{% hint style="info" icon="rectangle-api" %}
All calls mentioned on this page are on the [Control API](https://docs.toolkit.equs.ai/docs/presentation/configuration/pages/MvAvsnLQU3ER2hOIIDU2#id-2.4.1-apis).
{% endhint %}

## Update Verifier

> Update Verifier by ID

```json
{"openapi":"3.0.0","info":{"title":"EqusDTK Control API Documentation","version":"0.1.2"},"tags":[{"name":"Verifier","description":""}],"security":[{"sp":[]},{"tenant":[]},{"sp":[]}],"components":{"securitySchemes":{"sp":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Service Provider. Token of the Service Provider client in the `agents` realm, with no `tenantId` claim. Authorize one bearer scheme at a time."},"tenant":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Tenant. Token of a Tenant machine client or of a Tenant portal user, carrying the `tenantId` claim. Authorize one bearer scheme at a time."}},"schemas":{"UpdateVerifierRequest":{"type":"object","properties":{"name":{"type":"string","description":"Display name of the verifier."},"logo":{"description":"Logo displayed for this verifier when requesting presentations.","allOf":[{"$ref":"#/components/schemas/Logo"}]},"clientIdPrefix":{"type":"string","enum":["did_web","x509_hash","x509_san_dns"],"description":"OID4VP Client Identifier Prefix. Switching between the two x509 values reuses the existing certificate. Under `x509_hash` the Client Identifier is the certificate hash."},"trustedRootCerts":{"description":"Base64-encoded X.509 root certificates trusted when verifying presented credentials.","type":"array","items":{"type":"string"}}},"required":["name"]},"Logo":{"type":"object","properties":{"uri":{"type":"string","format":"uri","description":"Publicly accessible URL of the logo image."},"altText":{"type":"string","description":"Alternative text describing the logo, used for accessibility."}},"required":["uri"]},"UpdateVerifierResponse":{"type":"object","properties":{"id":{"type":"string","description":"Verifier unique ID"},"name":{"type":"string","description":"Display name of the verifier."},"logo":{"description":"Logo displayed for this verifier when requesting presentations.","allOf":[{"$ref":"#/components/schemas/Logo"}]},"didMethod":{"type":"string","description":"DID method used to generate the DID for this verifier.","enum":["did:web"]},"clientIdPrefix":{"type":"string","enum":["did_web","x509_hash","x509_san_dns"],"description":"OID4VP Client Identifier Prefix this verifier presents itself with.The x509 values are backed by a request-signing certificate;`did_web` resolves the identifier through the verifier DID document."},"status":{"type":"string","description":"Current status of the verifier.","enum":["active","deactivated"]},"requiresIntendedUse":{"type":"boolean","description":"Whether this verifier attests a registered purpose. Set by registering the first intended use. While true, every presentation request must name an intended use."},"createdAt":{"format":"date-time","type":"string","description":"Date and time the verifier was created."},"trustedRootCerts":{"description":"Base64-encoded X.509 root certificates trusted when verifying presented credentials.","type":"array","items":{"type":"string"}}},"required":["id","name","didMethod","clientIdPrefix","status","requiresIntendedUse","createdAt"]}}},"paths":{"/api/v1/verifiers/{id}":{"put":{"description":"Update Verifier by ID","operationId":"VerifierController_updateVerifier_api/v1","parameters":[{"name":"id","required":true,"in":"path","description":"Verifier unique ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateVerifierRequest"}}}},"responses":{"200":{"description":"When Verifier is updated successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateVerifierResponse"}}}},"400":{"description":"When the verifier name already exists."},"404":{"description":"When no verifier exists with such id."},"409":{"description":"When clientIdPrefix is changed to did_web while the verifier still has live intended uses."},"422":{"description":"When clientIdPrefix is x509_san_dns but the active access certificate carries no dNSName SAN equal to the protocol host."}},"summary":"Update Verifier","tags":["Verifier"]}}}}
```

## Your registration certificate

Every x509 Verifier on a deployment presents the **same** `client_id`, yours and every other Tenant's alike, because the certificate behind it belongs to the deployment rather than to you. So your registration certificate is the only thing in the request that says which organization is asking, and what it may ask for.

You get it from the national Registrar of the Member State where you're established, under [CIR (EU) 2025/848](https://eur-lex.europa.eu/eli/reg_impl/2025/848/oj/eng). You register a service, naming EQUS as your intermediary, and receive a certificate attesting the purpose you're authorized to ask for. **One certificate per intended use**.

{% hint style="warning" %}
**Tell the Registrar about the intermediary before they issue.** A wallet binds your registration certificate to the access certificate by comparing identifiers, so yours generally must carry an `intermediary` claim naming EQUS's identifier. Without it, a wallet compares EQUS's certificate against your own `sub`, sees they don't match, and refuses the request. The platform runs the same comparison when you register.
{% endhint %}

{% hint style="warning" %}
**Register it after the access certificate is active, not before.** If nothing is active there is nothing to compare against, the check is skipped, registration succeeds - and the wallet rejects the request later.
{% endhint %}

You register the certificate against your Verifier as an **intended use**: the Registrar's own identifier for the registration, a plain-language description of the purpose, and the certificate itself. All three are required. The registrar-assigned identifier is fixed once set - an update refuses to change it, because it names a registration rather than your own label for it.

A rejection comes back as a `422` naming its cause: a certificate that can't be authenticated, one missing a claim wallets require, or one not bound to the deployment's identity.

## \[Experimental] Register intended use

> Registers a purpose this Verifier is authorised to ask for, with the Registrar's certificate attesting it. The first registration marks the Verifier as requiring an intended use.

```json
{"openapi":"3.0.0","info":{"title":"EqusDTK Control API Documentation","version":"0.1.2"},"tags":[{"name":"Verifier","description":""}],"security":[{"sp":[]},{"tenant":[]},{"sp":[]}],"components":{"securitySchemes":{"sp":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Service Provider. Token of the Service Provider client in the `agents` realm, with no `tenantId` claim. Authorize one bearer scheme at a time."},"tenant":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Tenant. Token of a Tenant machine client or of a Tenant portal user, carrying the `tenantId` claim. Authorize one bearer scheme at a time."}},"schemas":{"CreateIntendedUseRequest":{"type":"object","properties":{"registrarIdentifier":{"type":"string","description":"The Registrar's identifier for this registration. Cannot be changed afterwards, and must be unique among this Verifier's live intended uses."},"purposeDescription":{"type":"string","description":"Why this Verifier asks for these attributes, in terms a person can read."},"registrationCertificate":{"type":"string","description":"The Relying Party Registration Certificate issued by the Registrar for this intended use, as a compact JWS. Stored verbatim, and accepted only if it authenticates against its own x5c chain, carries the claims a Wallet requires, and is bound to the active access certificate."}},"required":["registrarIdentifier","purposeDescription","registrationCertificate"]},"CreateIntendedUseResponse":{"type":"object","properties":{"id":{"type":"string","description":"Intended use unique ID"},"registrarIdentifier":{"type":"string","description":"The Registrar's own identifier for this registration. Immutable once the intended use exists."},"purposeDescription":{"type":"string","description":"Human-readable statement of why this Verifier asks for these attributes."},"registrationCertificate":{"type":"string","description":"The Relying Party Registration Certificate as a compact JWS, byte-identical to the one supplied. Emitted to wallets in the signed request."}},"required":["id","registrarIdentifier","purposeDescription","registrationCertificate"]}}},"paths":{"/api/v1/verifiers/{verifierId}/intended-uses":{"post":{"description":"Registers a purpose this Verifier is authorised to ask for, with the Registrar's certificate attesting it. The first registration marks the Verifier as requiring an intended use.","operationId":"IntendedUseController_createIntendedUse_api/v1","parameters":[{"name":"verifierId","required":true,"in":"path","description":"Verifier unique ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateIntendedUseRequest"}}}},"responses":{"201":{"description":"When the intended use is registered. The Verifier is marked as requiring an intended use from this point, so its x509 presentation requests must select one","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateIntendedUseResponse"}}}},"404":{"description":"When no Verifier with that identifier exists in the caller tenant context"},"409":{"description":"When an intended use with that registrar-assigned identifier is already live on this Verifier"},"422":{"description":"When the registration certificate cannot be authenticated (not a compact JWS of type rc-wrp+jwt, no x5c chain, or a signature that does not verify against the x5c leaf), when a claim a Wallet requires is missing or elapsed (entitlements, status.status_list, exp/nbf), or when it is bound to an entity other than the one the active access certificate identifies, or when the Verifier uses the did_web Client Identifier Prefix, which carries no verifier_info and so cannot attest a registered purpose"}},"summary":"[Experimental] Register intended use","tags":["Verifier"]}}}}
```

### Turning the requirement off again

**Your first registration changes how the Verifier behaves.** From that point it is marked as attesting a purpose, and *every* request must name one - a Verifier that has declared a purpose should never quietly stop declaring it.

The rule works in both directions: while intended uses are live, switching the prefix back to `did_web` is refused as well. To return to the access-certificate-only flow, clear the requirement explicitly, and do that **before** changing the prefix.

## \[Experimental] Stop attesting a purpose

> Clears the requires-an-intended-use mark, letting this Verifier issue x509 presentation requests that carry no registration certificate again. A deliberate downgrade in what it tells wallets, and logged as one.

```json
{"openapi":"3.0.0","info":{"title":"EqusDTK Control API Documentation","version":"0.1.2"},"tags":[{"name":"Verifier","description":""}],"security":[{"sp":[]},{"tenant":[]},{"sp":[]}],"components":{"securitySchemes":{"sp":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Service Provider. Token of the Service Provider client in the `agents` realm, with no `tenantId` claim. Authorize one bearer scheme at a time."},"tenant":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Tenant. Token of a Tenant machine client or of a Tenant portal user, carrying the `tenantId` claim. Authorize one bearer scheme at a time."}},"schemas":{"ClearIntendedUseRequirementResponse":{"type":"object","properties":{"verifierId":{"type":"string","description":"Verifier unique ID"},"wasRequired":{"type":"boolean","description":"Whether the Verifier had been attesting a purpose before this call. False means the call changed nothing."}},"required":["verifierId","wasRequired"]}}},"paths":{"/api/v1/verifiers/{verifierId}/intended-uses/clear-requirement":{"post":{"description":"Clears the requires-an-intended-use mark, letting this Verifier issue x509 presentation requests that carry no registration certificate again. A deliberate downgrade in what it tells wallets, and logged as one.","operationId":"IntendedUseController_clearIntendedUseRequirement_api/v1","parameters":[{"name":"verifierId","required":true,"in":"path","description":"Verifier unique ID","schema":{"type":"string"}}],"responses":{"200":{"description":"When the Verifier no longer has to attest a purpose. This is a downgrade in what it tells wallets and is logged as one","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClearIntendedUseRequirementResponse"}}}},"404":{"description":"When no Verifier with that identifier exists in the caller tenant context"}},"summary":"[Experimental] Stop attesting a purpose","tags":["Verifier"]}}}}
```

## Asking and sending the request

Your VP Schema is a normal VP Schema.

{% hint style="warning" %}
**Ask only for what your registration certificate authorizes.** A wallet compares the two and refuses the request if you ask for more. That check happens in the wallet, not here, so the platform cannot warn you about it.
{% endhint %}

Creating the request is identical to any other presentation request, with one addition: **`intendedUseId`** selects the purpose, and is required once your Verifier attests one.

The platform handles everything else. The request object is signed with the deployment's access certificate and carries its chain, `client_id` is derived from your prefix, and your registration certificate is included as a `verifier_info` entry - which is what the wallet reads to tell the Holder which organization is asking and what it is authorized to ask for.

Verifying the response is unchanged - the same two calls as for any other wallet. Verification uses the certificate recorded on the session rather than whatever is active when the response arrives, so a response that comes back after EQUS has rotated certificates still verifies.

## Trusting the issuers you verify

Wallets deciding whether to trust *you* is one half. The other is you deciding whose credentials to believe.

When a credential's Issuer is trusted through PKI rather than a DID - which is the case for mdoc credentials like the EUDI PID - verification checks that the Issuer chains to a root on your Verifier's `trustedRootCerts` list. That list is **per Verifier and empty by default**, so the first presentation fails until you add the Issuer's root.

Which roots you need is decided by the ecosystem, not by us: a Member State Trusted List, a scheme operator, or the issuing authority directly.

You set the list with the same [Update Verifier](#put-api-v1-verifiers-id) call used above.

{% hint style="info" %}
This is independent of everything else on this page. It applies to any Verifier presented with a PKI-anchored credential, whatever its Client Identifier Prefix - switching to x509 doesn't populate the list, and you don't have to switch to use it.
{% endhint %}

## When something doesn't work

**These are yours to fix:**

<table data-search="false"><thead><tr><th>Error</th><th>Fix</th></tr></thead><tbody><tr><td><code>no_intended_use_selected</code></td><td>Pass <code>intendedUseId</code>, or clear the requirement</td></tr><tr><td><code>prefix_san_host_mismatch</code></td><td>Use <code>x509_hash</code></td></tr><tr><td><code>duplicate_registrar_identifier</code></td><td>That identifier is already registered on this Verifier</td></tr><tr><td><code>registration_certificate_*</code></td><td>The certificate itself - see <a href="#your-registration-certificate">Your registration certificate</a></td></tr><tr><td><code>intended_use_requires_x509</code></td><td>Clear the requirement before switching the prefix back</td></tr><tr><td>Issuer root untrusted</td><td>Add the Issuer's root to <code>trustedRootCerts</code></td></tr><tr><td>Wallet rejects the request for asking too much</td><td>Your VP Schema asks for more than your certificate authorizes</td></tr></tbody></table>

**Contact EQUS for these.** They all come down to the deployment's access certificate, which you don't control. Send the error code - there's nothing to change on your side.

<table data-search="false"><thead><tr><th>Error</th><th>What it means</th></tr></thead><tbody><tr><td><code>no_request_signing_cert</code></td><td>No access certificate is active on the deployment</td></tr><tr><td><code>request_signing_cert_revoked</code></td><td>The certificate has been revoked</td></tr><tr><td><code>request_signing_cert_not_valid</code></td><td>The certificate is expired, or not yet valid</td></tr><tr><td><code>request_signing_cert_san_mismatch</code><br><code>response_uri_fqdn_mismatch</code></td><td>The certificate was issued for a different host than this deployment</td></tr></tbody></table>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.toolkit.equs.ai/docs/presentation/configuration/configuration-eudi-ecosystem.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
