Issue credentials
This guide covers OID4VCI issuance only. Complete these linked prerequisites before starting:
- Platform foundations and authentication boundaries for the tenant service token and the wallet-token boundary.
- Tenant provisioning and confidential-client access for the tenant and hosted authorization server.
- Credential designs and claims for the design, format, and claims.
- Status-list creation and lifecycle for any status reference; create the list before the issuer binds it.
- Authorization-server configuration for the AS, clients, grants, and issuer binding.
- Issuer configuration for the issuer identity, signing selection, and credential configuration.
For presentation requests and verification results, continue with Verifier and DCQL configuration and Trust-domain creation and verifier binding.
Full pre-authorized-code flow
This is the smallest complete issuance run. It starts with an already configured issuer and ends with a credential plus the status reference that a verifier must evaluate.
- Read issuer metadata anonymously. Resolve
credential_issuer,credential_endpoint,token_endpoint(or the AS metadata URL), the chosencredential_configuration_id, supported proof algorithms, and the advertised format. - Create an offer with the tenant token. Select exactly one configuration and the
pre-authorized_codegrant. Store the returnedcredential_offerURI and offer/session id. - Resolve the offer anonymously. The wallet fetches the URI and confirms that the offer's
configuration id and grant match the issuer metadata. Do not use
tenantAccessTokenhere. - Exchange the pre-authorized code. POST to the token endpoint with the code. Store the
response
access_tokenaswalletAccessTokenand its expiry. The token response is not the nonce contract: do not expect or cache a genericc_noncefrom it. This token belongs only to this issuance session. - Resolve the issuer nonce contract. If issuer metadata advertises
nonce_endpoint, POST to that exact endpoint and read the top-levelc_noncefrom its response. The endpoint is a single issuer capability, not a format-specific or flow-specific URL. Ifnonce_endpointis absent, the issuer has not advertised a c_nonce requirement and the proof must omit thenonceclaim. - Build the wallet proof. Create a holder key, sign the proof JWT with the algorithm advertised
by the configuration, set
audto the credential issuer, and, only when the nonce endpoint returned one, setnonceto thatc_nonce. A tenant service token is not a proof of possession. - Request the credential. POST the selected
credential_configuration_id, requested subject claims, and proof tocredential_endpoint, withBearer walletAccessToken. - Validate the response locally. Confirm the response format, issuer, configuration/type
identifier, claims, validity, signature, and
credentialStatusURI/index. Save the credential in the wallet only after those checks pass. - Verify separately. The offer and credential response do not prove presentation. Use the verifier/DCQL guide to request a presentation, then evaluate signature, holder binding, trust, validity and current status.
The response-derived chain is:
issuer metadata -> credential_offer -> pre-authorized_code
-> walletAccessToken -> (POST nonce_endpoint -> c_nonce, when advertised)
-> credential request
-> credential + credentialStatus(status_list, status_list_index)
The canonical Postman collection follows this chain in 15 Issue SD-JWT VC and mdoc, 16 Issue W3C VC, and the later format/grant folders. The tenant token is used for offer administration;
the wallet token is used only for token/credential protocol calls. Each token request stores its
own response and clears incompatible stale session variables.
Postman correlation: The canonical EDK Enterprise collection keeps authorization-server setup in 09 Authorization Server Configuration, issuer setup in 10 Issuer Configuration, designs in 11 Credential Designs, standalone status publication in 12 Status Lists, issuer credential bindings in 13 Credential Configurations, and the wallet protocol in 15 Issue SD-JWT VC and mdoc (with 16 to 20 for the other grant and format profiles). In the focused folder, run 01 Create EuPid offer, 02 Resolve credential offer, 03 Fetch OID4VCI metadata, 04 Exchange pre-authorized code for token, 05 Request EuPid credential, and 06 Check EuPid offer status; the mdoc lane is 07 through 10. The wallet token is stored as walletAccessToken and is never reused for administration.
Follow the issuance path
Treat issuance as a chain of identifiers, not as one form submission. The design supplies the
credential_configuration_id; the issuer metadata publishes that configuration; the offer binds
the wallet to it; the token response authorizes the credential request; and the final response
contains the format-specific credential plus any status reference. Carry IDs and URIs forward from
responses. Do not derive them from a display name or by incrementing an index.
The Console Test surface is useful for an operator walkthrough. The REST protocol is the integration boundary for a wallet or issuer service. The tabs below show the same material action through each surface; a Console success is not evidence that an external wallet completed the protocol.
Choose the credential format
Use the same format from the credential design through the credential request.
| Credential | OID4VCI format | Type identifier | Status publication |
|---|---|---|---|
| SD-JWT VC | dc+sd-jwt | vct | JWT Token Status List, application/statuslist+jwt |
| ISO mdoc | mso_mdoc | docType | CWT Token Status List, application/statuslist+cwt |
| W3C VCDM 1.1 JWT | jwt_vc_json | VCDM 1.1 context and types | Bitstring Status List credential, application/vc+jwt |
| W3C VCDM 2.0 JWT | jwt_vc_json-ld | VCDM 2.0 context and types | Bitstring Status List credential, application/vc+jwt |
For mdoc, each requested claim includes its namespace and element name. For example, org.iso.18013.5.1/family_name is different from the JSON claim family_name.
Confirm the issuer configuration
- Admin Console
- REST
Issuer setup is intentionally outside this issuance guide. Follow Issuer configuration, read the issuer metadata, and confirm the selected configuration and grant are advertised before creating an offer.
Use the OID4VCI REST API reference to inspect the published metadata and issuance contracts. Configuration writes belong to the dedicated issuer guide.
Issue with a pre-authorized code
- Admin Console
- REST
- Open the issuer Test page.
- Select the credential configuration and a pre-authorized-code request template.
- Enter the claims required by the design.
- Create the offer and give its URI to the wallet.
- Open the issuance session to inspect its current state.
Create the offer, resolve it, exchange the pre-authorized code, and request the credential with a proof produced by the wallet.
The offer request needs the issuer instance, credential_configuration_id, grant type, and the
claims/template inputs required by the design. The offer response is the source of truth for the
offer URI and pre-authorized code. Resolve that URI before exchanging it. The access token is
response-derived. If issuer metadata advertises nonce_endpoint, POST that endpoint and use its
top-level c_nonce in the proof; if it is absent, omit the proof nonce. Never guess or cache
either value across issuance sessions.
- Overview
- Request
- Response
Create EuPid offer
Endpoint: POST /api/oid4vci/v1/backend/credential/offers
Captured response: 201 Created
This captured endpoint is shown from the E2E run; it is not mapped to one of the generated EDK REST API reference pages.
Connect an environment to rewrite this call to real service bases and run it.
- Overview
- Request
- Response
Resolve credential offer
Endpoint: GET /oid4vci/acme/oid4vci/credentials/offers/00000000-0000-4000-8000-000000000000
Captured response: 200 OK
This captured endpoint is shown from the E2E run; it is not mapped to one of the generated EDK REST API reference pages.
Connect an environment to rewrite this call to real service bases and run it.
- Overview
- Request
- Response
Exchange pre-authorized code for token
Endpoint: POST /as/acme/token
Captured response: 200 OK
This captured endpoint is shown from the E2E run; it is not mapped to one of the generated EDK REST API reference pages.
Connect an environment to rewrite this call to real service bases and run it.
- Overview
- Request
- Response
Request EuPid credential
Endpoint: POST /oid4vci/acme/oid4vci/credential
Captured response: 200 OK
This captured endpoint is shown from the E2E run; it is not mapped to one of the generated EDK REST API reference pages.
Connect an environment to rewrite this call to real service bases and run it.
For mdoc, select the mso_mdoc configuration and send the namespaced claims defined by its design:
- Overview
- Request
- Response
Create Mdl offer
Endpoint: POST /api/oid4vci/v1/backend/credential/offers
Captured response: 201 Created
This captured endpoint is shown from the E2E run; it is not mapped to one of the generated EDK REST API reference pages.
Connect an environment to rewrite this call to real service bases and run it.
- Overview
- Request
- Response
Request Mdl credential
Endpoint: POST /oid4vci/acme/oid4vci/credential
Captured response: 200 OK
This captured endpoint is shown from the E2E run; it is not mapped to one of the generated EDK REST API reference pages.
Connect an environment to rewrite this call to real service bases and run it.
If a response is missing its credential, has an unexpected format, omits a required proof nonce, or returns a status reference that cannot be resolved, stop the flow. Do not treat an HTTP success or an offer URI as issuance success.
Issue with an authorization code
Use this grant when the wallet must authenticate the holder or obtain consent before issuance.
- Admin Console
- REST
Select the authorization server in the issuer settings or request template. Confirm the client, scopes, redirect URI, and claim mappings used by the credential configuration, then create the offer from the issuer Test page.
The offer contains an authorization_code grant. The wallet reads the authorization-server metadata, completes authorization with PKCE, exchanges the code, and sends the credential request.
- Overview
- Request
- Response
Create offer with authorization code grant
Endpoint: POST /api/oid4vci/v1/backend/credential/offers
Captured response: 201 Created
This captured endpoint is shown from the E2E run; it is not mapped to one of the generated EDK REST API reference pages.
Connect an environment to rewrite this call to real service bases and run it.
- Overview
- Request
- Response
Fetch authorization server metadata
Endpoint: GET /.well-known/oauth-authorization-server
Captured response: 200 OK
This captured endpoint is shown from the E2E run; it is not mapped to one of the generated EDK REST API reference pages.
Connect an environment to rewrite this call to real service bases and run it.
The authorization response supplies the code that is exchanged at the token endpoint. Validate issuer, client, redirect URI, PKCE binding, scopes, and nonce before requesting a credential. A missing or mismatched value is fail-closed: do not fall back to pre-authorized issuance.
Check the issued credential
After the credential request succeeds:
- Confirm that the response format matches the requested
credential_configuration_id. - Decode the credential with a library that supports that format.
- Check the issuer, type identifier, validity period, claims, and status URI and index.
- Store the credential in the wallet.
For mdoc, check the docType, namespace-qualified elements, DSC chain, and x5chain. For W3C credentials, check the selected VCDM context and the expected credential structure.
For SD-JWT VC, check the vct, issuer, disclosure digests, and JWT Token Status List URI/index.
For ISO mdoc, check the docType, namespace-qualified elements, issuer-signed items, DSC chain,
and CWT Token Status List URI/index. For W3C VCDM 1.1, check the JSON-LD context and VC/credential
subject shape; for VCDM 2.0, check the VCDM 2.0 context and data-model terms. A verifier must
reject an unknown format, wrong type identifier, invalid signature, expired credential, or
unresolvable status URI; it must not silently reinterpret one format as another.