Issuing Credentials and Interacting With Wallets
Issuing a credential means creating a credential offer for one or more credential configurations and letting a recipient claim it into their wallet. The platform implements the OpenID4VCI pre-authorized code flow: the wallet scans a QR code (or follows an openid-credential-offer:// deep link), optionally enters a PIN, and the issuer signs and returns the credential in the configured format (SD-JWT VC or JWT-VC-JSON). This page walks through the full flow against the live API.
All endpoints below require a valid Bearer token, sent as Authorization: Bearer <token> (see Authentication).
1. Prerequisites
Before you can create a credential offer you need:
An issuer DID — a
did:webordid:keycreated through the agent API and hosted/served by the service.A registered issuer — bound to that DID (
POST /agent/issuer).At least one schema assigned to the issuer — assigning a schema produces one or more credential configurations.
A schema combined with a format yields a credential configuration id of the form Name@Version:format, for example [email protected]:sd-jwt. Credential offers reference these configuration ids. See the schema documentation for how to create and assign schemas.
In the examples below, replace :issuerDid with your issuer's DID (e.g. did:web:issuer.example.com:1f2e... or did:key:z6Mk...).
2. Create a Credential Offer
POST /issuers/:issuerDid/credential-offers
Authorization: Bearer <token>
Content-Type: application/jsonRequest Body
credentialConfigurationIds(array of strings, required): One or more credential configuration ids to include in the offer. At least one is required. Example:["[email protected]:sd-jwt"]requirePin(boolean, optional): Whentrue, the pre-authorized code is protected by a PIN (transaction code) that the holder must enter in their wallet. Defaults tofalse.issuanceMetadata(object, optional): Metadata passed to the credential mapper. UseclaimsByConfigurationIdto supply the claim values for each configuration in the offer.
Example Request
Response Body
credentialOfferUri(string): Anopenid-credential-offer://URI. Render it as a QR code or present it as a deep link so a wallet can start the issuance flow.issuanceSessionId(string): The identifier of the issuance session created for this offer. Use it to track issuance progress (see Track Issuance Sessions).credentialConfigurationIds(array of strings): The credential configuration ids included in the offer.userPin(string, optional): Returned only whenrequirePinistrue. Deliver this PIN to the holder out-of-band; they must enter it in their wallet to claim the credential.
3. Generate a QR Code
To render the offer as a scannable image without building your own QR encoder, call the QR-code variant. It accepts the same request body as the offer endpoint and returns the QR directly as a PNG.
The response is an image/png body (a 400×400 QR encoding the credentialOfferUri). The offer details are returned in response headers:
X-Credential-Offer-Uri— theopenid-credential-offer://URI encoded in the QR.X-Issuance-Session-Id— the issuance session id for this offer.X-Pin— the PIN, present only whenrequirePinwastrue.
You can also regenerate a QR for an existing session at any time:
This returns an image/png for the session's offer URI, with the X-Credential-Offer-Uri header set.

4. Wallet Claims the Credential
Once the offer is presented, the recipient claims it with an SSI-compatible wallet:
The wallet scans the QR code or opens the
openid-credential-offer://link.It reads the credential offer and the issuer metadata, then runs the OpenID4VCI pre-authorized code flow against the issuer's authorization server.
If the offer required a PIN, the wallet prompts the holder for the PIN (the value returned as
userPin/ theX-Pinheader).The issuer signs the credential for the requested configuration and returns it to the wallet in the configured format — SD-JWT VC or JWT-VC-JSON.
The wallet stores the credential for later presentation to verifiers.
The wallet handles token exchange and credential retrieval internally as part of the OpenID4VCI flow; there is no separate client-side token endpoint to call. From the issuer's side, you only create the offer and (optionally) track the session.
Two things happen automatically at claim time:
The service injects an
authorized_userclaim (authorizedUserin the JWT-VC-JSON credential subject) into every issued credential payload, set from thesubof the wallet's access token, so each credential records which authenticated user claimed it.For custom schemas, the claim values are validated against the schema: required claims must be present and each claim must match its declared type. A validation failure aborts issuance and moves the session to the
Errorstate.
The platform also provides a cloud (server-side) wallet. A holder tenant can claim an offer programmatically by posting the offer URI to
POST /holders/:holderId/processwith body{ "uri": "openid-credential-offer://..." }. See the holder/wallet documentation for details.
5. Track Issuance Sessions
Each offer creates an issuance session whose state advances as the wallet completes the flow.
List sessions for an issuer
Get a single session
Response Body
id(string): The session identifier (theissuanceSessionIdreturned at offer creation).issuerId(string): The issuer DID associated with the session.state(string): The current state of the issuance session (e.g.OfferCreated,Completed).credentialOfferUri(string): The offer URI tied to the session.createdAt/expiresAt(string): ISO 8601 timestamps for creation and expiry.userPin(string, optional): The PIN, when the session used a PIN-protected pre-authorized code.errorMessage(string, optional): Present only when the session ended in theErrorstate; omitted otherwise.issuedCredentials(array of strings): The credential configuration ids issued so far in this session (e.g.[email protected]:sd-jwt). Empty until credentials are issued; fully populated once the session reachesCompleted.
6. Direct Signing (optional)
When you need a signed credential without the offer/wallet flow — for example to embed a credential in another system or to issue server-to-server — sign it directly. The issuer signs the payload with its DID's key and returns the credential in compact serialized form.
Request Body
format(string, required): One ofsd-jwt-vcorjwt_vc_json.payload(object, required): The credential claims. For SD-JWT VC, include avct(verifiable credential type) value.subjectDid(string, optional): The DID of the credential subject, used as the subject identifier in the credential.disclosureFrame(object, optional): SD-JWT only. An object with an_sdarray listing the claim keys that should be selectively disclosable.
Example Request
Response Body
credential(string): The signed credential in compact serialized form (a compact JWT forjwt_vc_json, or an SD-JWT with appended disclosures forsd-jwt-vc).format(string): The canonical credential format identifier of the signed credential. This is not the request enum value — it is the format the credential is actually issued in:dc+sd-jwtfor an SD-JWT VC (requestformat: "sd-jwt-vc"), andjwt_vcfor a JWT-VC-JSON credential (requestformat: "jwt_vc_json").
That is the whole flow: create an offer, present it as a QR or deep link, and let the wallet claim it — tracking progress through the issuance session. For one-off, non-interactive issuance, use the direct signing endpoint.
Last updated