Processing URIs
A cloud wallet (a holder) interacts with issuers and verifiers by processing a URI — the same openid-credential-offer://, openid4vp://, or https:// content that a mobile wallet would obtain by scanning a QR code or following a deep link. A single endpoint handles both directions: claiming offered credentials from an issuer (OpenID4VCI) and responding to a presentation request from a verifier (OpenID4VP). The wallet inspects the URI scheme and runs the matching protocol automatically, so the caller never has to know in advance whether a given QR code is an offer or a request.
This page is the endpoint reference — request and response bodies, options, and errors. For a step-by-step walkthrough of what happens inside each flow, see Processing Credential Offers and Presentation Requests.
All routes in this section are scoped to one holder under /holders/:holderId and require a valid Bearer token with the admin role. Ownership is enforced, so a user can only process URIs for holders they own.
A holder is created beforehand via POST /agent/holder (see Holders and Tenancy). The holderId returned there is the tenant id used in the path below.
Processing a URI
POST /holders/:holderId/process
Accepts a URI scanned from a QR code, copied from a deep link, or received directly from an issuer or verifier. The endpoint dispatches on the URI scheme:
openid-credential-offer://...— accepts a credential offer. The wallet resolves the offer, requests an access token, requests the offered credential(s), binds them to the holder's DID, and stores them.openid4vp://...orhttps://...— responds to an authorization request. The wallet resolves the request, selects matching credentials, builds the presentation, and submits it.
Any other scheme returns 400 Bad Request with the message Unknown URI protocol. Expected openid-credential-offer:// or openid4vp://.
Request Body
uri (string, required): the URI to process. Supports both
openid-credential-offer://...credential offers andopenid4vp://...(orhttps://...) authorization requests — the same content a mobile wallet would scan.
curl -X POST http://localhost:3000/holders/594b464b-20bd-495a-9fb6-87328c759629/process \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{"uri": "openid-credential-offer://?credential_offer_uri=https://issuer.example.com/offers/abc123"}'Response Body — Credential Offer
When the URI is a credential offer, the wallet claims the offered credential(s) and returns:
status:
successon completion.claimed: number of credentials stored from the offer.
ids: wallet record identifiers of the newly stored credentials, usable to correlate entries returned by the credential listing endpoints.
For example, processing an [email protected]:sd-jwt offer stores one SD-JWT VC, and the decoded listing then shows its vct and selectively disclosable claims such as employee_id, full_name, and department.
Response Body — Authorization Request
When the URI is an authorization request, the wallet selects the credentials that satisfy the verifier's query and sends the presentation. Selection is automatic — the caller never supplies a credential — and uses DIF Presentation Exchange v2 or DCQL, depending on the query carried by the request. The response is:
status:
successon completion.action:
presentation_sent.
Prerequisites and Errors
Credential offers require the holder to have at least one DID, used to bind the issued credential. If the holder has no DID, the request fails with
400 Bad Request(Holder has no DIDs created. Cannot accept credential offer.). A DID is created when the holder is registered viaPOST /agent/holder.Authorization requests require the holder to already hold a credential that satisfies the verifier's query. If nothing in the wallet matches the Presentation Exchange definition or DCQL query, the presentation cannot be built.
An unrecognized URI scheme returns
400 Bad Request(Unknown URI protocol. Expected openid-credential-offer:// or openid4vp://).
Notes
The endpoint runs the full protocol synchronously and returns the final result. There is no separate initialize/confirm step and no per-call flow lifecycle to track.
Isolation between callers comes from holder ownership on the route, not from a per-flow session — only the owner of a holder can process URIs for it.
The same URI content that this endpoint consumes is exactly what an end user's mobile wallet would scan. Issuers expose offer URIs and PNG QR codes from their credential-offer endpoints, and verifiers expose authorization-request URIs and PNG QR codes from their authorization-request endpoints.
After processing an offer, inspect what the holder now holds with
GET /holders/:holderId/credentials(raw) andGET /holders/:holderId/credentials/decoded(decoded). See Credential Management.
Last updated