iGrant.io OpenID4VC API - Holder
When to use
Use this skill when your organisation holds credentials in its own wallet. It
covers the 31 operations of the Holder group: the holder global
configuration, credential receipt and lifecycle, the credential offer, issuer
metadata, wallet unit status, the presentation flow, and the wallet
notifications.
Use it when you must:
- take an
openid-credential-offer:// URI into the wallet, and finish a flow
that needs a transaction code, a deferred credential, or an authorization code;
- list, read, accept, or delete the credentials of the wallet;
- turn automatic presentation on or off for one credential;
- answer an
openid4vp:// request: receive it, filter the matching credentials,
and send the selection;
- watch credential lifecycle events over the notification stream.
For the issuing side, read igrantio-api-issuer. For the relying-party side,
read igrantio-api-verifier.
Protocol scope
- The wallet implements OpenID4VCI 1.0 for issuance and OpenID4VP 1.0
with DCQL for presentation. Send a presentation with the V3 operations.
- Presentation Exchange is not a supported surface of an iGrant.io verifier. The
filter and send operations still hold an
inputDescriptors shape, and it
applies only to a request from an outside verifier that still sends a DIF
Presentation Exchange definition. Use the DCQL shape for every iGrant.io
request.
- Honest exceptions. Two holder operations still work on draft versions:
- The create credential offer operation accepts
draft_13 (default) and
draft_17 only. It refuses version_01 with HTTP 400.
Use draft_13 until the backend adds 1.0 support.
- The read credential issuer metadata operation takes
version with the values
draft_13 (default), draft_15 and draft_17.
A credential record can therefore carry a version of draft_13,
draft_15, draft_17 or version_01.
Authentication
Send the API key in the Authorization header with the ApiKey prefix and a
space:
Authorization: ApiKey <your-api-key>
Demo base URL: https://demo-api.igrant.io. The bundle also lists
https://api.igrant.io (production) and https://staging-api.igrant.io.
The API key stays on your server. The browser never holds it.
Correlation ids
| Id |
Where it comes from |
What it names |
credentialId |
receive credential response (credentialId, and id holds the same value) |
the credential record in this wallet. Every /credential/{credentialId}/... path uses it. |
credentialExchangeId |
receive credential response |
the OID4VCI exchange on the issuer side. Use it to line the record up with the issuance history of the issuer. |
presentationId |
V3 receive verification response (presentationId, and id holds the same value) |
the presentation record in this wallet. The filter, send, read and delete paths use it. |
presentationExchangeId |
V3 receive verification response |
the OpenID4VP exchange on the verifier side. |
credential.presentationId |
receive credential response |
the presentation that a dynamic credential request asks for during issuance. |
Endpoint reference
Holder global configuration
supportCredentialEncryption is mandatory on create. The other fields are
refreshCredentialStatusInterval (seconds between two revocation status checks;
0 stops the automatic checks), redirectUrl, and
requirePushedAuthorizationRequests (default false, and true makes the
wallet use a Pushed Authorization Request in the Authorization Code flow). The
create operation ignores redirectUrl and stores an empty string; set it with
the update operation.
Receive and hold credentials
| Method |
Path |
Purpose |
Documentation |
POST |
/v2/config/digital-wallet/openid/sdjwt/credential/receive |
Take a Credential Offer into the wallet and start the OID4VCI flow. |
Receive credential |
PUT |
/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/user-pin |
Supply the transaction code of a Pre-Authorized Code Flow. |
Receive credential with user PIN |
PUT |
/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/receive-deferred |
Fetch a credential from the Deferred Credential Endpoint. No request body. |
Receive deferred credential |
POST |
/v2/config/digital-wallet/openid/sdjwt/credential/exchange-code |
Exchange an authorization code for the token and the credential. |
Exchange code and receive credential |
PUT |
/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/accept |
Accept a received credential and send the OID4VCI notification to the issuer. No request body. |
Accept credential |
PUT |
/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/configure |
Turn automatic presentation on or off for one credential. |
Configure auto presentation |
PUT |
/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/request |
Ask the issuer for a new credential when the held one expires. No request body. |
Request credential re-issuance |
GET |
/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId} |
Read one credential record. |
Read credential |
GET |
/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/revocation-status |
Read the revocation status of a held credential. |
Read credential revocation status |
DELETE |
/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId} |
Remove a credential from the wallet. |
Delete credential |
GET |
/v2/config/digital-wallet/openid/sdjwt/credentials |
List the credentials of the wallet. Filter with expired, credentialStatus, vct, doctype, type, search, sortOrder, offset and limit. |
List credential |
The wallet has no separate deny operation. To refuse an offer, delete the
credential record. The wallet marks the record as deleted, leaves it out of the
list, stops using it for presentations, and tells the issuance backend in the
background, so the response does not show the result of that step.
Credential offer, issuer metadata, and wallet unit
| Method |
Path |
Purpose |
Documentation |
POST |
/v2/config/digital-wallet/openid/sdjwt/credential-offer |
Make a Credential Offer for a credential issuer. |
Create credential offer |
GET |
/v2/config/digital-wallet/openid/credential-issuer |
Read and check the Credential Issuer Metadata of an issuer. |
Read credential issuer metadata |
GET |
/v2/config/digital-wallet/openid/wallet-unit/status |
Read the wallet unit status of this organisation. |
Read wallet unit status |
Presentations
| Method |
Path |
Purpose |
Documentation |
POST |
/v3/config/digital-wallet/openid/sdjwt/verification/receive |
Take an Authorization Request URI and make a presentation record. |
Receive verification |
POST |
/v2/config/digital-wallet/openid/sdjwt/verification/{presentationId}/filter |
List the credentials of the wallet that answer each credential query. No request body. |
Filter verification |
POST |
/v3/config/digital-wallet/openid/sdjwt/verification/{presentationId}/send |
Send the selected credentials as the Verifiable Presentation. |
Send verification |
GET |
/v3/config/digital-wallet/openid/sdjwt/verification/{presentationId} |
Read one presentation record. |
Read verification |
GET |
/v3/config/digital-wallet/openid/sdjwt/verifications |
List the presentation records. Filter with status, search, sortOrder, offset and limit. |
List verification |
DELETE |
/v2/config/digital-wallet/openid/sdjwt/verification/{presentationId} |
Delete a presentation record. The response has no body. |
Delete verification |
The filter and delete operations have no V3 version. Use them also for a record
that the V3 receive operation made.
Notifications
| Method |
Path |
Purpose |
Documentation |
GET |
/v2/config/digital-wallet/openid/notifications |
List the wallet notifications. Filter with status, notificationType, search, sortOrder, offset and limit. |
List notification |
GET |
/v2/config/digital-wallet/openid/notifications/sse |
Stream new notifications over Server-Sent Events. |
Stream notifications (SSE) |
GET |
/v2/config/digital-wallet/openid/notification/{notificationId} |
Read one notification. |
Read notification |
PUT |
/v2/config/digital-wallet/openid/notification/{notificationId} |
Mark one notification as read. |
Update notification |
DELETE |
/v2/config/digital-wallet/openid/notification/{notificationId} |
Delete one notification. |
Delete notification |
DELETE |
/v2/config/digital-wallet/openid/notifications |
Delete every notification. |
Delete all notifications |
Key fields
Receive a credential
POST /v2/config/digital-wallet/openid/sdjwt/credential/receive
credentialOffer is mandatory: the full openid-credential-offer:// URI, or the
credential offer URI that the issuer publishes. The wallet also accepts the field
name CredentialOffer with a capital C. The other fields are autoPresent
(default false), trustAnchor (did:key or x509; default did:key), and kid.
The response holds credential, which is one object, or an array when the offer
carries more than one credential configuration. Fields to read:
credentialId and credentialExchangeId - the correlation ids. id holds the
same value as credentialId.
credentialStatus - offer_sent, offer_received, token_issued,
credential_issued, credential_pending, credential_acked,
credential_accepted, credential_deleted or issuance_denied.
userPinRequired and txCode (length, input_mode, description) - read
them to know whether you must call the user PIN operation, and which length and
character set the issuer expects.
deferredEndpoint and acceptanceToken - set for a deferred issuance.
authorizationRequest and oAuthFlow (frontchannel or backchannel) - set
for the Authorization Code Flow.
credentialFormat - dc+sd-jwt, vc+sd-jwt, vp+sd-jwt, jwt_vc_json,
jwt_vc or mso_mdoc.
revocationStatus - Operational, Revoked or Suspended.
version - draft_13, draft_15, draft_17 or version_01.
issuer (name, location, cover, logo, description),
credentialIssuer, credentialConfigurations, credential,
credentialToken, autoPresent, presentationId, isWalletUnitAttestation,
isVerifiedWithTrustList, trustServiceProvider, requiresEncryption,
expiredCredentials, expiredCredentialTokens, legalPidAttestation,
legalPidAttestationPop, legalPidVerified.
Every field is always present. A field with no value comes back with its empty
value.
Finish a flow that needs more input
- Transaction code.
PUT /v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/user-pin
takes the mandatory userPin. The wallet strips the leading and trailing
spaces, and the remaining value must hold 4 to 12 characters.
- Deferred credential.
PUT /v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/receive-deferred
has no request body.
- Authorization code.
POST /v2/config/digital-wallet/openid/sdjwt/credential/exchange-code takes the
mandatory code. credentialIds names the credential records to complete; the
wallet falls back to the records that match state when you leave it out.
state is nullable: send null or leave it out when the redirect carries no
state.
- Auto present.
PUT /v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/configure
takes the mandatory boolean autoPresent. false makes the holder approve
each presentation.
Create a credential offer
POST /v2/config/digital-wallet/openid/sdjwt/credential-offer
credentialIssuer is mandatory: the Credential Issuer Identifier whose metadata
the holder reads. version accepts draft_13 (default) and draft_17 only;
the service refuses version_01 with HTTP 400.
credentialConfigurationIds names the entries of
credential_configurations_supported, and it belongs to draft_13 only.
scope selects the credential definitions by scope, and it belongs to
draft_17.
Read credential issuer metadata
GET /v2/config/digital-wallet/openid/credential-issuer takes the mandatory
credentialIssuer query parameter and the optional version (draft_13
default, draft_15, draft_17). The wallet reads the
.well-known/openid-credential-issuer document, checks it against the metadata
model of that version, drops every key that the model does not define, including
signed_metadata, and returns each missing optional key with the value null.
An entry of display gets Not Discoverable for name and description when
the issuer gives no value. credential_configurations_supported passes through
unchanged.
Read wallet unit status
GET /v2/config/digital-wallet/openid/wallet-unit/status returns status:
not_installed (no wallet deployment), installed (deployed, but no valid
Wallet Unit Attestation), operational (a valid Wallet Unit Attestation, but no
LPID credential), or valid (both).
Answer a presentation request
1. Receive. POST /v3/config/digital-wallet/openid/sdjwt/verification/receive
takes the mandatory vpTokenQrCode: the openid4vp:// Authorization Request URI
that the holder scanned or opened. The other fields are autoPresent (default
false; the wallet skips the automatic step when the request asks for an ID
Token), kid, and trustAnchor (did:key or x509; default did:key).
The response holds presentation. Fields to read: presentationId,
presentationExchangeId, status (presentation_pending or
presentation_acked), verified, dcqlQuery, responseType (vp_token,
id_token, device_response), clientId, clientIdScheme, clientMetadata,
nonce, requestUri, redirectUri, responseRedirectUri, encryptionJwk,
encryptedResponseEncValuesSupported, encryptionAlgorithm, transactionData,
transactionDataDecoded, presentationValidity, verifierAttestation,
trustAnchor, kid, idToken and idTokenDecoded.
2. Filter.
POST /v2/config/digital-wallet/openid/sdjwt/verification/{presentationId}/filter
has no request body; the wallet discards one if you send it. The response holds
credentials when the verifier sent a DCQL query, which is the usual answer.
It holds inputDescriptors in place of it only for an outside verifier that
still sends a Presentation Exchange definition. The wallet never returns both
keys. Each entry holds id, name, purpose and matchedCredentials. The
array holds one entry for every credential query, so an empty
matchedCredentials marks a requirement that the holder cannot meet.
3. Send.
POST /v3/config/digital-wallet/openid/sdjwt/verification/{presentationId}/send
takes one of two shapes. Use the DCQL shape when the presentation record holds a
dcqlQuery:
{
"credentials": [
{
"id": "Person Identification Data - PID",
"credentialId": "d29a6c1e-4b8f-4a1d-9c3e-7f5b2a0d8e11"
}
]
}
credentials is mandatory, and each entry needs id: the DCQL credential
query that it answers. Use the id that the filter operation returned, or
dcqlQuery.credentials[].id from the record.
- Give
credentialId for a query that takes one credential, or credentialIds
for a query that sets multiple to true. Do not give both.
claimIds names the claim path pointers to disclose. Send it only when the
credential query holds claim_sets. Without it, the wallet discloses the
claims that the query asks for.
encryptionAlgorithm accepts ECDH-ES. Send it only when the verifier asks
for an encrypted response; otherwise the wallet uses the algorithm that the
verifier advertised.
The legacy shape takes inputDescriptors, with id and credentialId on each
entry. Use it only for an outside Presentation Exchange verifier.
Notifications
Each notification holds id, notificationType (credential_pending,
credential_acked, credential_revoked, credential_expired),
notificationTypeRelatedId, notificationContent, status (read or
unread), createdAt and updatedAt. The list response also holds
pagination (currentPage, totalItems, totalPages, limit, hasPrevious,
hasNext). The update operation takes the mandatory status, and it accepts
only the value read.
The SSE stream. GET /v2/config/digital-wallet/openid/notifications/sse
keeps the connection open and writes one frame for each new notification.
- The browser
EventSource API cannot set request headers. The server reads the
Authorization header first, and it falls back to the authorization query
parameter only when that header is empty. Give the scheme prefix and the space
in the value: ApiKey <key> or Bearer <token>. The stream accepts both
schemes.
- Response headers:
Content-Type: text/event-stream,
Cache-Control: no-cache, Connection: keep-alive.
- Frames, in order:
retry: 30000 first, before the server checks the
organisation; then event: connected with the data {} once the subscription
is live; then one event: notification frame for each new notification, with
an id field that holds the stream sequence number and a data field that
holds the notification object; a : keep-alive comment line every 15 seconds,
which clients ignore; and event: error when the stream fails.
Sandbox call style
Holder functionality is split for sandbox organisations:
- The v2 holder credential operations do not work in a sandbox context.
This covers receive, receive-deferred, user PIN, exchange-code, accept,
auto-present, list, read, delete, reissuance, the credential offer, the
issuer metadata read, and the holder-side filter and verification delete.
When the call carries a sandbox context (a bound API key), the service
rejects it with "Holder functionalities for sandbox organisation is
disabled". These operations do not take the
X-SandboxOrgId header.
- The v3 presentation operations support the sandbox context (receive,
read, send, list). With a bearer access token, send
X-SandboxOrgId: <sandboxOrgId>. With an API key, bind the key first with
PUT /v2/config/admin/apikey/{apiKeyId}/sandbox-org; the header itself is
ignored under API-key authentication.
- The holder global configuration and notification operations also support
the sandbox context in the same way.
Read igrantio-api-sandboxes for the sandbox organisation lifecycle and the
full call-style rules.
Cross-references
igrantio-ows-overview - architecture, glossary, and the shared contracts.
igrantio-holder-backend - runnable tenant backend for these endpoints
(API-key-hiding proxy + notifications SSE relay).
igrantio-holder-frontend - the holder portal UI: wallet views, the DCQL
share wizard, notifications inbox, trust badges.
igrantio-holder-notifications - notifications building block (SSE relay
- browser client + the notification-to-action decision table).
igrantio-api-issuer - the issuer that makes the credential offer.
igrantio-api-verifier - the verifier that sends the Authorization Request.
igrantio-api-wallet-provider - the wallet deployment and the Wallet Unit
Attestation behind the wallet unit status.
igrantio-api-key-management - the wallet key that kid names.
igrantio-api-sandboxes - the sandbox organisation lifecycle.
Documentation is the source of truth
This skill mirrors the iGrant.io OpenID4VC API documentation. If this skill and
the linked documentation disagree, the documentation wins. Before you rely
on a payload shape, fetch the linked page, or the raw specification at
https://docs.igrant.io/openapispecifications/oid4vc.yaml, and check for
updates. Follow the documentation and report the drift so this skill can be
corrected.
1---2name: igrantio-api-holder3description: Holder group of the iGrant.io OpenID4VC API: take a credential offer into the organisation wallet (receive, user PIN, deferred, authorization-code exchange), list, read, accept, configure auto-present, re-issue and delete held credentials, read credential issuer metadata and wallet unit status, make a credential offer, and answer an OpenID4VP request with the V3 receive, filter and send operations, plus the wallet notification list and the Server-Sent Events stream. Use this skill when your organisation acts as the holder wallet, or whenever you need the exact holder path, correlation id, or enum value.4license: Apache-2.05---67# iGrant.io OpenID4VC API - Holder89## When to use10Use this skill when your organisation holds credentials in its own wallet. It11covers the 31 operations of the **Holder** group: the holder global12configuration, credential receipt and lifecycle, the credential offer, issuer13metadata, wallet unit status, the presentation flow, and the wallet14notifications.1516Use it when you must:17- take an `openid-credential-offer://` URI into the wallet, and finish a flow18 that needs a transaction code, a deferred credential, or an authorization code;19- list, read, accept, or delete the credentials of the wallet;20- turn automatic presentation on or off for one credential;21- answer an `openid4vp://` request: receive it, filter the matching credentials,22 and send the selection;23- watch credential lifecycle events over the notification stream.2425For the issuing side, read `igrantio-api-issuer`. For the relying-party side,26read `igrantio-api-verifier`.2728## Protocol scope29- The wallet implements **OpenID4VCI 1.0** for issuance and **OpenID4VP 1.0**30 with **DCQL** for presentation. Send a presentation with the **V3** operations.31- Presentation Exchange is not a supported surface of an iGrant.io verifier. The32 filter and send operations still hold an `inputDescriptors` shape, and it33 applies only to a request from an outside verifier that still sends a DIF34 Presentation Exchange definition. Use the DCQL shape for every iGrant.io35 request.36- **Honest exceptions.** Two holder operations still work on draft versions:37 - The create credential offer operation accepts `draft_13` (default) and38 `draft_17` only. It refuses `version_01` with HTTP 400.39 Use `draft_13` until the backend adds 1.0 support.40 - The read credential issuer metadata operation takes `version` with the values41 `draft_13` (default), `draft_15` and `draft_17`.42 A credential record can therefore carry a `version` of `draft_13`,43 `draft_15`, `draft_17` or `version_01`.4445## Authentication46Send the API key in the `Authorization` header with the `ApiKey` prefix and a47space:4849```50Authorization: ApiKey <your-api-key>51```5253Demo base URL: `https://demo-api.igrant.io`. The bundle also lists54`https://api.igrant.io` (production) and `https://staging-api.igrant.io`.55The API key stays on your server. The browser never holds it.5657## Correlation ids58| Id | Where it comes from | What it names |59| --- | --- | --- |60| `credentialId` | receive credential response (`credentialId`, and `id` holds the same value) | the credential record in this wallet. Every `/credential/{credentialId}/...` path uses it. |61| `credentialExchangeId` | receive credential response | the OID4VCI exchange on the issuer side. Use it to line the record up with the issuance history of the issuer. |62| `presentationId` | V3 receive verification response (`presentationId`, and `id` holds the same value) | the presentation record in this wallet. The filter, send, read and delete paths use it. |63| `presentationExchangeId` | V3 receive verification response | the OpenID4VP exchange on the verifier side. |64| `credential.presentationId` | receive credential response | the presentation that a dynamic credential request asks for during issuance. |6566## Endpoint reference6768### Holder global configuration6970| Method | Path | Purpose | Documentation |71| --- | --- | --- | --- |72| `POST` | `/v2/config/digital-wallet/openid/holder/global-configuration` | Create the holder global configuration. | [Create holder global configuration](https://docs.igrant.io/docs/openid4vc-api/config-create-digital-wallet-open-id-holder-global-configuration/) |73| `GET` | `/v2/config/digital-wallet/openid/holder/global-configuration/{globalConfigurationId}` | Read the holder global configuration. | [Read holder global configuration](https://docs.igrant.io/docs/openid4vc-api/config-read-digital-wallet-open-id-holder-global-configuration/) |74| `PUT` | `/v2/config/digital-wallet/openid/holder/global-configuration/{globalConfigurationId}` | Update the holder global configuration. | [Update holder global configuration](https://docs.igrant.io/docs/openid4vc-api/config-update-digital-wallet-open-id-holder-global-configuration/) |75| `DELETE` | `/v2/config/digital-wallet/openid/holder/global-configuration/{globalConfigurationId}` | Delete the holder global configuration. | [Delete holder global configuration](https://docs.igrant.io/docs/openid4vc-api/config-delete-digital-wallet-open-id-holder-global-configuration/) |76| `GET` | `/v2/config/digital-wallet/openid/holder/global-configurations` | List the holder global configuration. | [List holder global configuration](https://docs.igrant.io/docs/openid4vc-api/config-list-digital-wallet-open-id-holder-global-configuration/) |7778`supportCredentialEncryption` is mandatory on create. The other fields are79`refreshCredentialStatusInterval` (seconds between two revocation status checks;80`0` stops the automatic checks), `redirectUrl`, and81`requirePushedAuthorizationRequests` (default `false`, and `true` makes the82wallet use a Pushed Authorization Request in the Authorization Code flow). The83create operation ignores `redirectUrl` and stores an empty string; set it with84the update operation.8586### Receive and hold credentials8788| Method | Path | Purpose | Documentation |89| --- | --- | --- | --- |90| `POST` | `/v2/config/digital-wallet/openid/sdjwt/credential/receive` | Take a Credential Offer into the wallet and start the OID4VCI flow. | [Receive credential](https://docs.igrant.io/docs/openid4vc-api/config-receive-digital-wallet-open-id-credential/) |91| `PUT` | `/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/user-pin` | Supply the transaction code of a Pre-Authorized Code Flow. | [Receive credential with user PIN](https://docs.igrant.io/docs/openid4vc-api/config-receive-user-pin-for-digital-wallet-open-id-credential/) |92| `PUT` | `/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/receive-deferred` | Fetch a credential from the Deferred Credential Endpoint. No request body. | [Receive deferred credential](https://docs.igrant.io/docs/openid4vc-api/config-receive-deferred-digital-wallet-open-id-credential/) |93| `POST` | `/v2/config/digital-wallet/openid/sdjwt/credential/exchange-code` | Exchange an authorization code for the token and the credential. | [Exchange code and receive credential](https://docs.igrant.io/docs/openid4vc-api/config-digital-wallet-open-id-exchange-code-and-receive-credential/) |94| `PUT` | `/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/accept` | Accept a received credential and send the OID4VCI notification to the issuer. No request body. | [Accept credential](https://docs.igrant.io/docs/openid4vc-api/config-accept-digital-wallet-open-id-credential/) |95| `PUT` | `/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/configure` | Turn automatic presentation on or off for one credential. | [Configure auto presentation](https://docs.igrant.io/docs/openid4vc-api/config-configure-auto-present-for-digital-wallet-open-id-credential/) |96| `PUT` | `/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/request` | Ask the issuer for a new credential when the held one expires. No request body. | [Request credential re-issuance](https://docs.igrant.io/docs/openid4vc-api/config-request-digital-wallet-open-id-credential-reissuance/) |97| `GET` | `/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}` | Read one credential record. | [Read credential](https://docs.igrant.io/docs/openid4vc-api/config-read-digital-wallet-open-id-credential/) |98| `GET` | `/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/revocation-status` | Read the revocation status of a held credential. | [Read credential revocation status](https://docs.igrant.io/docs/openid4vc-api/config-read-digital-wallet-open-id-credential-revocation-status/) |99| `DELETE` | `/v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}` | Remove a credential from the wallet. | [Delete credential](https://docs.igrant.io/docs/openid4vc-api/config-delete-digital-wallet-open-id-credential/) |100| `GET` | `/v2/config/digital-wallet/openid/sdjwt/credentials` | List the credentials of the wallet. Filter with `expired`, `credentialStatus`, `vct`, `doctype`, `type`, `search`, `sortOrder`, `offset` and `limit`. | [List credential](https://docs.igrant.io/docs/openid4vc-api/config-list-digital-wallet-open-id-credential/) |101102The wallet has no separate deny operation. To refuse an offer, delete the103credential record. The wallet marks the record as deleted, leaves it out of the104list, stops using it for presentations, and tells the issuance backend in the105background, so the response does not show the result of that step.106107### Credential offer, issuer metadata, and wallet unit108109| Method | Path | Purpose | Documentation |110| --- | --- | --- | --- |111| `POST` | `/v2/config/digital-wallet/openid/sdjwt/credential-offer` | Make a Credential Offer for a credential issuer. | [Create credential offer](https://docs.igrant.io/docs/openid4vc-api/config-digital-wallet-open-id-holder-create-credential-offer/) |112| `GET` | `/v2/config/digital-wallet/openid/credential-issuer` | Read and check the Credential Issuer Metadata of an issuer. | [Read credential issuer metadata](https://docs.igrant.io/docs/openid4vc-api/config-digital-wallet-open-id-read-issuer-metadata/) |113| `GET` | `/v2/config/digital-wallet/openid/wallet-unit/status` | Read the wallet unit status of this organisation. | [Read wallet unit status](https://docs.igrant.io/docs/openid4vc-api/config-read-digital-wallet-open-id-wallet-unit-status/) |114115### Presentations116117| Method | Path | Purpose | Documentation |118| --- | --- | --- | --- |119| `POST` | `/v3/config/digital-wallet/openid/sdjwt/verification/receive` | Take an Authorization Request URI and make a presentation record. | [Receive verification](https://docs.igrant.io/docs/openid4vc-api/config-receive-digital-wallet-open-id-verification-v-3/) |120| `POST` | `/v2/config/digital-wallet/openid/sdjwt/verification/{presentationId}/filter` | List the credentials of the wallet that answer each credential query. No request body. | [Filter verification](https://docs.igrant.io/docs/openid4vc-api/config-filter-digital-wallet-open-id-verification/) |121| `POST` | `/v3/config/digital-wallet/openid/sdjwt/verification/{presentationId}/send` | Send the selected credentials as the Verifiable Presentation. | [Send verification](https://docs.igrant.io/docs/openid4vc-api/config-send-digital-wallet-open-id-verification-presentation-v-3/) |122| `GET` | `/v3/config/digital-wallet/openid/sdjwt/verification/{presentationId}` | Read one presentation record. | [Read verification](https://docs.igrant.io/docs/openid4vc-api/config-read-digital-wallet-open-id-verification-v-3/) |123| `GET` | `/v3/config/digital-wallet/openid/sdjwt/verifications` | List the presentation records. Filter with `status`, `search`, `sortOrder`, `offset` and `limit`. | [List verification](https://docs.igrant.io/docs/openid4vc-api/config-list-digital-wallet-open-id-verification-v-3/) |124| `DELETE` | `/v2/config/digital-wallet/openid/sdjwt/verification/{presentationId}` | Delete a presentation record. The response has no body. | [Delete verification](https://docs.igrant.io/docs/openid4vc-api/config-delete-digital-wallet-open-id-verification/) |125126The filter and delete operations have no V3 version. Use them also for a record127that the V3 receive operation made.128129### Notifications130131| Method | Path | Purpose | Documentation |132| --- | --- | --- | --- |133| `GET` | `/v2/config/digital-wallet/openid/notifications` | List the wallet notifications. Filter with `status`, `notificationType`, `search`, `sortOrder`, `offset` and `limit`. | [List notification](https://docs.igrant.io/docs/openid4vc-api/config-list-digital-wallet-open-id-notification/) |134| `GET` | `/v2/config/digital-wallet/openid/notifications/sse` | Stream new notifications over Server-Sent Events. | [Stream notifications (SSE)](https://docs.igrant.io/docs/openid4vc-api/config-list-digital-wallet-open-id-notification-sse/) |135| `GET` | `/v2/config/digital-wallet/openid/notification/{notificationId}` | Read one notification. | [Read notification](https://docs.igrant.io/docs/openid4vc-api/config-read-digital-wallet-open-id-notification/) |136| `PUT` | `/v2/config/digital-wallet/openid/notification/{notificationId}` | Mark one notification as read. | [Update notification](https://docs.igrant.io/docs/openid4vc-api/config-update-digital-wallet-open-id-notification/) |137| `DELETE` | `/v2/config/digital-wallet/openid/notification/{notificationId}` | Delete one notification. | [Delete notification](https://docs.igrant.io/docs/openid4vc-api/config-delete-digital-wallet-open-id-notification/) |138| `DELETE` | `/v2/config/digital-wallet/openid/notifications` | Delete every notification. | [Delete all notifications](https://docs.igrant.io/docs/openid4vc-api/config-delete-all-digital-wallet-open-id-notification/) |139140## Key fields141142### Receive a credential143`POST /v2/config/digital-wallet/openid/sdjwt/credential/receive`144145`credentialOffer` is mandatory: the full `openid-credential-offer://` URI, or the146credential offer URI that the issuer publishes. The wallet also accepts the field147name `CredentialOffer` with a capital `C`. The other fields are `autoPresent`148(default `false`), `trustAnchor` (`did:key` or `x509`; default `did:key`), and `kid`.149150The response holds `credential`, which is one object, or an array when the offer151carries more than one credential configuration. Fields to read:152153- `credentialId` and `credentialExchangeId` - the correlation ids. `id` holds the154 same value as `credentialId`.155- `credentialStatus` - `offer_sent`, `offer_received`, `token_issued`,156 `credential_issued`, `credential_pending`, `credential_acked`,157 `credential_accepted`, `credential_deleted` or `issuance_denied`.158- `userPinRequired` and `txCode` (`length`, `input_mode`, `description`) - read159 them to know whether you must call the user PIN operation, and which length and160 character set the issuer expects.161- `deferredEndpoint` and `acceptanceToken` - set for a deferred issuance.162- `authorizationRequest` and `oAuthFlow` (`frontchannel` or `backchannel`) - set163 for the Authorization Code Flow.164- `credentialFormat` - `dc+sd-jwt`, `vc+sd-jwt`, `vp+sd-jwt`, `jwt_vc_json`,165 `jwt_vc` or `mso_mdoc`.166- `revocationStatus` - `Operational`, `Revoked` or `Suspended`.167- `version` - `draft_13`, `draft_15`, `draft_17` or `version_01`.168- `issuer` (`name`, `location`, `cover`, `logo`, `description`),169 `credentialIssuer`, `credentialConfigurations`, `credential`,170 `credentialToken`, `autoPresent`, `presentationId`, `isWalletUnitAttestation`,171 `isVerifiedWithTrustList`, `trustServiceProvider`, `requiresEncryption`,172 `expiredCredentials`, `expiredCredentialTokens`, `legalPidAttestation`,173 `legalPidAttestationPop`, `legalPidVerified`.174175Every field is always present. A field with no value comes back with its empty176value.177178### Finish a flow that needs more input179- **Transaction code.**180 `PUT /v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/user-pin`181 takes the mandatory `userPin`. The wallet strips the leading and trailing182 spaces, and the remaining value must hold 4 to 12 characters.183- **Deferred credential.**184 `PUT /v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/receive-deferred`185 has no request body.186- **Authorization code.**187 `POST /v2/config/digital-wallet/openid/sdjwt/credential/exchange-code` takes the188 mandatory `code`. `credentialIds` names the credential records to complete; the189 wallet falls back to the records that match `state` when you leave it out.190 `state` is nullable: send `null` or leave it out when the redirect carries no191 state.192- **Auto present.**193 `PUT /v2/config/digital-wallet/openid/sdjwt/credential/{credentialId}/configure`194 takes the mandatory boolean `autoPresent`. `false` makes the holder approve195 each presentation.196197### Create a credential offer198`POST /v2/config/digital-wallet/openid/sdjwt/credential-offer`199200`credentialIssuer` is mandatory: the Credential Issuer Identifier whose metadata201the holder reads. `version` accepts `draft_13` (default) and `draft_17` only;202the service refuses `version_01` with HTTP 400.203`credentialConfigurationIds` names the entries of204`credential_configurations_supported`, and it belongs to `draft_13` only.205`scope` selects the credential definitions by scope, and it belongs to206`draft_17`.207208### Read credential issuer metadata209`GET /v2/config/digital-wallet/openid/credential-issuer` takes the mandatory210`credentialIssuer` query parameter and the optional `version` (`draft_13`211default, `draft_15`, `draft_17`). The wallet reads the212`.well-known/openid-credential-issuer` document, checks it against the metadata213model of that version, drops every key that the model does not define, including214`signed_metadata`, and returns each missing optional key with the value `null`.215An entry of `display` gets `Not Discoverable` for `name` and `description` when216the issuer gives no value. `credential_configurations_supported` passes through217unchanged.218219### Read wallet unit status220`GET /v2/config/digital-wallet/openid/wallet-unit/status` returns `status`:221`not_installed` (no wallet deployment), `installed` (deployed, but no valid222Wallet Unit Attestation), `operational` (a valid Wallet Unit Attestation, but no223LPID credential), or `valid` (both).224225### Answer a presentation request226**1. Receive.** `POST /v3/config/digital-wallet/openid/sdjwt/verification/receive`227takes the mandatory `vpTokenQrCode`: the `openid4vp://` Authorization Request URI228that the holder scanned or opened. The other fields are `autoPresent` (default229`false`; the wallet skips the automatic step when the request asks for an ID230Token), `kid`, and `trustAnchor` (`did:key` or `x509`; default `did:key`).231232The response holds `presentation`. Fields to read: `presentationId`,233`presentationExchangeId`, `status` (`presentation_pending` or234`presentation_acked`), `verified`, `dcqlQuery`, `responseType` (`vp_token`,235`id_token`, `device_response`), `clientId`, `clientIdScheme`, `clientMetadata`,236`nonce`, `requestUri`, `redirectUri`, `responseRedirectUri`, `encryptionJwk`,237`encryptedResponseEncValuesSupported`, `encryptionAlgorithm`, `transactionData`,238`transactionDataDecoded`, `presentationValidity`, `verifierAttestation`,239`trustAnchor`, `kid`, `idToken` and `idTokenDecoded`.240241**2. Filter.**242`POST /v2/config/digital-wallet/openid/sdjwt/verification/{presentationId}/filter`243has no request body; the wallet discards one if you send it. The response holds244`credentials` when the verifier sent a DCQL query, which is the usual answer.245It holds `inputDescriptors` in place of it only for an outside verifier that246still sends a Presentation Exchange definition. The wallet never returns both247keys. Each entry holds `id`, `name`, `purpose` and `matchedCredentials`. The248array holds one entry for every credential query, so an empty249`matchedCredentials` marks a requirement that the holder cannot meet.250251**3. Send.**252`POST /v3/config/digital-wallet/openid/sdjwt/verification/{presentationId}/send`253takes one of two shapes. Use the DCQL shape when the presentation record holds a254`dcqlQuery`:255256```json257{258 "credentials": [259 {260 "id": "Person Identification Data - PID",261 "credentialId": "d29a6c1e-4b8f-4a1d-9c3e-7f5b2a0d8e11"262 }263 ]264}265```266267- `credentials` is mandatory, and each entry needs `id`: the DCQL credential268 query that it answers. Use the `id` that the filter operation returned, or269 `dcqlQuery.credentials[].id` from the record.270- Give `credentialId` for a query that takes one credential, or `credentialIds`271 for a query that sets `multiple` to `true`. Do not give both.272- `claimIds` names the claim path pointers to disclose. Send it only when the273 credential query holds `claim_sets`. Without it, the wallet discloses the274 claims that the query asks for.275- `encryptionAlgorithm` accepts `ECDH-ES`. Send it only when the verifier asks276 for an encrypted response; otherwise the wallet uses the algorithm that the277 verifier advertised.278279The legacy shape takes `inputDescriptors`, with `id` and `credentialId` on each280entry. Use it only for an outside Presentation Exchange verifier.281282### Notifications283Each notification holds `id`, `notificationType` (`credential_pending`,284`credential_acked`, `credential_revoked`, `credential_expired`),285`notificationTypeRelatedId`, `notificationContent`, `status` (`read` or286`unread`), `createdAt` and `updatedAt`. The list response also holds287`pagination` (`currentPage`, `totalItems`, `totalPages`, `limit`, `hasPrevious`,288`hasNext`). The update operation takes the mandatory `status`, and it accepts289only the value `read`.290291**The SSE stream.** `GET /v2/config/digital-wallet/openid/notifications/sse`292keeps the connection open and writes one frame for each new notification.293294- The browser `EventSource` API cannot set request headers. The server reads the295 `Authorization` header first, and it falls back to the `authorization` query296 parameter only when that header is empty. Give the scheme prefix and the space297 in the value: `ApiKey <key>` or `Bearer <token>`. The stream accepts both298 schemes.299- Response headers: `Content-Type: text/event-stream`,300 `Cache-Control: no-cache`, `Connection: keep-alive`.301- Frames, in order: `retry: 30000` first, before the server checks the302 organisation; then `event: connected` with the data `{}` once the subscription303 is live; then one `event: notification` frame for each new notification, with304 an `id` field that holds the stream sequence number and a `data` field that305 holds the notification object; a `: keep-alive` comment line every 15 seconds,306 which clients ignore; and `event: error` when the stream fails.307308## Sandbox call style309Holder functionality is split for sandbox organisations:310311- **The v2 holder credential operations do not work in a sandbox context.**312 This covers receive, receive-deferred, user PIN, exchange-code, accept,313 auto-present, list, read, delete, reissuance, the credential offer, the314 issuer metadata read, and the holder-side filter and verification delete.315 When the call carries a sandbox context (a bound API key), the service316 rejects it with "Holder functionalities for sandbox organisation is317 disabled". These operations do not take the `X-SandboxOrgId` header.318- **The v3 presentation operations support the sandbox context** (receive,319 read, send, list). With a bearer access token, send320 `X-SandboxOrgId: <sandboxOrgId>`. With an API key, bind the key first with321 `PUT /v2/config/admin/apikey/{apiKeyId}/sandbox-org`; the header itself is322 ignored under API-key authentication.323- The holder global configuration and notification operations also support324 the sandbox context in the same way.325326Read `igrantio-api-sandboxes` for the sandbox organisation lifecycle and the327full call-style rules.328329## Cross-references330- `igrantio-ows-overview` - architecture, glossary, and the shared contracts.331- `igrantio-holder-backend` - runnable tenant backend for these endpoints332 (API-key-hiding proxy + notifications SSE relay).333- `igrantio-holder-frontend` - the holder portal UI: wallet views, the DCQL334 share wizard, notifications inbox, trust badges.335- `igrantio-holder-notifications` - notifications building block (SSE relay336 + browser client + the notification-to-action decision table).337- `igrantio-api-issuer` - the issuer that makes the credential offer.338- `igrantio-api-verifier` - the verifier that sends the Authorization Request.339- `igrantio-api-wallet-provider` - the wallet deployment and the Wallet Unit340 Attestation behind the wallet unit status.341- `igrantio-api-key-management` - the wallet key that `kid` names.342- `igrantio-api-sandboxes` - the sandbox organisation lifecycle.343344## Documentation is the source of truth345This skill mirrors the iGrant.io OpenID4VC API documentation. If this skill and346the linked documentation disagree, **the documentation wins**. Before you rely347on a payload shape, fetch the linked page, or the raw specification at348<https://docs.igrant.io/openapispecifications/oid4vc.yaml>, and check for349updates. Follow the documentation and report the drift so this skill can be350corrected.351352- Holder API index: <https://docs.igrant.io/docs/developer-apis/>353- Holder notification handling: `igrantio-holder-notifications`.