Skip to main content
Version: v0.25.0 (Latest)

Issuing

With the two VC channels in place (Channels), the issuer now renders a credential design from each one and offers them to a wallet. The central idea on this page: in the enterprise model, the design is derived from the VC channel, not authored beside it. You supply only what the channel does not carry: an optional alias, the hosting mode, and a reference back to the channel. There is no claims[] array, no bindings[] array, and no displays in the request; the converter reads the channel's claimMappings, traverses the set selection and the profile roles, and takes the localized display (name, description, logo, colors) from the channel's branding.

MethodPathDescription
POST/api/v1/designs/credentials/fromchannelRender a credential design from a set-bound VC channel
POST/oid4vci/backend/credential/offersCreate a credential offer for one or more configurations

All calls use the operator's OIDC bearer token. The design-from-channel render is a license-gated enterprise write (it derives, persists, and records lineage); offer creation is a standard issuer backend call.

Rendering a Design From a VC Channel

The converter derives the claim list, selective-disclosure policy, ordering, credential-type identity, and the localized display directly from the VC channel you created in Channels (the display comes from the channel's brandingRef). The request body carries only what the channel does not already encode: an optional alias, the hostingMode, and a channelRef pointing at the VC channel by id and version.

The converter reads the channel's claimMappings (each entry linking a set traversal path to a wire claim path with a disclosure policy), resolves those paths through the set, the profile roles, and the catalog entities, and emits:

  • the design claim list, with selective-disclosure policy and ordering flowing from the claimMappings and catalog governance metadata, and
  • the credential-type identity (format, vct for SD-JWT, or doctype for mdoc) taken directly from the VC channel.

It also writes one ISSUE-role usage-lineage row for each consumed traversal path. See Provenance and Operations.

Render the Employee SD-JWT design

POST /api/v1/designs/credentials/fromchannel
{
"channelRef": {
"channelId": "11aa...employeeSdjwtChannelId",
"channelVersion": 1
},
"alias": "Employee Credential",
"hostingMode": "LOCAL"
}

Request fields

FieldRequiredNotes
channelRefyesObject with channelId and channelVersion pointing at the set-bound VC channel.
aliasnoA short internal label for the design.
hostingModeyesLOCAL for a self-hosted design in this walkthrough.
No claims and no display in the request

The absence of a claims[] array and a displays array is intentional. The claim list, selective-disclosure policy, ordering, and credential-type identity are derived from the VC channel and the catalog; the localized wallet display (name, description, logo, colors) comes from the channel's brandingRef. This is what keeps the issuer and verifier in agreement, and the display consistent across formats: all of it is authored on the model, not on the design.

The eight derived claims reflect the Employee SD-JWT VC channel's claimMappings. Note that address.country is a two-segment path derived from the set traversal ["residentialAddress","country"] (crossing the catalog's residentialAddress relationship from Employee to Address), and employer is derived from ["legal_name"] on the employer role.

Render the Business Card mdoc design

POST /api/v1/designs/credentials/fromchannel
{
"channelRef": {
"channelId": "22bb...businessCardMdocChannelId",
"channelVersion": 1
},
"alias": "Business Card",
"hostingMode": "LOCAL"
}

The five mdoc claims come from the Business Card VC channel's claimMappings. postal_code is derived from the set traversal ["residentialAddress","postal_code"], crossing the same residentialAddress relationship used by the SD-JWT design. Selective disclosure in mdoc is handled by the namespace and element structure at issuance, so the converter emits the mdoc-appropriate policy rather than SD-JWT disclosures.

Both designs trace back to the same Acme Employee set and profile, and each binds a distinct credentialConfigurationId (EmployeeCredential, BusinessCardMdoc) that the issuer advertises in /.well-known/openid-credential-issuer via the OID4VCI issuance channel created in the previous step.

Creating the Offers

A credential offer invites a wallet to obtain a credential. Create one offer per credential configuration: a wallet obtains a single credential per offer, so the Employee credential and the Business Card are offered separately. Each offer uses the grant its credential configuration declared: the Employee credential via authorization code (the wallet authenticates as the employee first), the Business Card via a pre-authorized code (the offer carries the code, no interactive login).

The credential_configuration_ids used below (EmployeeCredential, BusinessCardMdoc) are the ids the OID4VCI issuance channel assigned to each composed credential in Channels (L4); the issuer advertises them in /.well-known/openid-credential-issuer, and each design above carries its id as credentialConfigurationId.

Offer the Employee credential

POST /oid4vci/backend/credential/offers
{
"credential_configuration_ids": ["EmployeeCredential"],
"issuer_id": "http://acme.localhost:8088",
"grants": { "authorization_code": {} },
"ttl_seconds": 600
}

Offer the Business Card credential

The Business Card offer targets the other configuration. Its OID4VCI channel declared a pre-authorized grant, so this offer carries a pre-authorized code rather than requiring an interactive login:

{
"credential_configuration_ids": ["BusinessCardMdoc"],
"issuer_id": "http://acme.localhost:8088",
"grants": { "pre_authorized_code": { "tx_code_required": true } },
"ttl_seconds": 600
}

Each offer returns its own correlation_id and offer_uri.

Request fields

FieldRequiredNotes
credential_configuration_ids[]yesThe configuration this offer is for. Create one offer per credential; an offer carries a single credential.
issuer_idyesThe OID4VCI issuer identifier.
grantsyesThe grant for this offer, matching the credential configuration: { "authorization_code": {} } (the wallet authenticates first) or { "pre_authorized_code": { "tx_code_required": true } } (the offer carries a pre-authorized code).
ttl_secondsnoOffer lifetime in seconds.

The offer_uri is the OID4VCI offer deeplink. It can be rendered as a QR code, a link, an NFC tag, or a deeplink; the rendering is a presentation choice and does not change the offer.

Static offers

For a "one URL, many holders" scenario, the offer create accepts a uriLifecycle of SINGLE_USE (default) or REUSABLE_FRESH_PER_FETCH. See the OpenID4VCI REST API for the compact offer endpoint and its options.

How Issuance Proceeds

The wallet drives the rest of the flow over the OpenID4VCI protocol. The protocol legs themselves are unchanged from the standard issuer and are documented in the OpenID4VCI guide. In summary, the wallet:

  1. Fetches /.well-known/openid-credential-issuer and /.well-known/oauth-authorization-server.
  2. Runs the authorization-code and PKCE login as the employee.
  3. Exchanges the code at /token for an access token.
  4. Generates an ephemeral holder key and requests a c_nonce from /oid4vci/nonce.
  5. Builds a JWT proof of possession over the issuer id and c_nonce with the holder key.
  6. Requests the credential at /oid4vci/credential (carrying the proof) and receives the signed credential.

Those six legs are the authorization-code flow, which the Employee credential uses. The Business Card credential is configured pre-authorized: the wallet skips the login (steps 2 and 3), takes the pre-authorized code from the offer, exchanges it at /token (entering the tx_code if required), then continues from the nonce and proof steps.

The holder obtains each credential from its own offer. The issued SD-JWT carries the selected attributes as selective-disclosure entries, its iss and kid identify the issuer key, and its holder binding (cnf.jwk) is the wallet's ephemeral public key. The mdoc is signed with an X.509 x5chain (a CA-signed leaf), and the issuer binds the wallet's device public key into the mdoc MSO. Both credentials are then presented together in Verifying.

The claim shape each credential carries is exactly the shape the design derived from the VC channel, which is the same shape the verifier will request in Verifying because the verifier's DCQL query derives from the same VC channels.

See also

The designs rendered here derive from the set-bound VC channels created in Channels. The OID4VCI issuance channel created in that same step composes both VC channels and is what the issuer uses to advertise the EmployeeCredential and BusinessCardMdoc configuration ids.

Next Steps

  • Verifying: author a multi-credential DCQL from the same channels, request a presentation, and read the verified data
  • Provenance and Operations: the ISSUE-role lineage rows this render wrote, and governance flow-through
  • OpenID4VCI: the full issuer protocol and the attribute pipeline