For the complete documentation index, see llms.txt. This page is also available as Markdown.

OID4VC Service API

The oid4vc-service implements OpenID for Verifiable Credential Issuance (OID4VCI) and OpenID for Verifiable Presentations (OID4VP) for the Sunbird RC stack. It issues and verifies credentials in multiple formats — jwt_vc_json, ldp_vc, vc+sd-jwt, and mso_mdoc — and supports the W3C VC Render Method for rendering credentials visually.

Prerequisite: the full Sunbird RC stack must be running (db, vault, redis, identity-service, credential-schema, credential-service, oid4vc-service)

Base URLs

Variable
Default value
Service

identity_base

http://localhost:3332

identity-service

schema_base

http://localhost:3333

credential-schema service

credential_base

http://localhost:3000

credentials-service

oid4vc_base

http://localhost:3400

oid4vc-service


0. Discovery

Read-only endpoints for service health and OID4VC/OIDC metadata discovery.

Health check

GET {{oid4vc_base}}/health

Returns 200 OK when the service is up.

Issuer metadata

GET {{oid4vc_base}}/.well-known/openid-credential-issuer

Returns the OID4VCI Credential Issuer Metadata document (supported credential configurations, formats, endpoints).

Authorization Server metadata

Returns the OAuth2/OIDC Authorization Server metadata document.

JWKS

Returns the issuer's public JSON Web Key Set, used by wallets/verifiers to validate signatures.


1. Setup — Schemas & DIDs

Endpoints on identity-service and credential-schema used to prepare issuer/holder DIDs and an OID4VCI-enabled credential schema before running any issuance flow.

Generate a DID

Body

Pass "keyPairType": "JsonWebKey2020" to generate an EC P-256 key pair (required for mso_mdoc issuer DIDs — see Section 6).

Response — array of DID Documents, e.g. id: "did:rcw:...". Used to generate issuer, holder, and impostor DIDs.

Create an OID4VCI-enabled credential schema

Body

The key part enabling OID4VCI is the oid4vciConfig block:

Field
Description

oid4vciEnabled

Must be true for the schema to be offerable via /oid4vc/offer.

oid4vciFormats

One or more of jwt_vc_json, ldp_vc, vc+sd-jwt, mso_mdoc.

display

Human-readable display metadata surfaced in issuer metadata / wallets.

vct

(SD-JWT only) the Verifiable Credential Type identifier.

mdoc

(mso_mdoc only) { docType, namespace } per ISO/IEC 18013-5.

renderMethod

(optional) embeds a W3C VC Render Method template.

Response201 Created with the persisted schema, including schema.id.

List OID4VCI-enabled schemas

Returns an array of schemas that have oid4vciEnabled: true, each including schemaId, formats, and (where applicable) mdoc.docType / mdoc.namespace. Used by wallets/issuers to discover what can be offered.


2. OID4VCI — Credential Issuance Flow

The core issuance flow (pre-authorized code grant), demonstrated here for jwt_vc_json but identical in shape for ldp_vc, vc+sd-jwt, and mso_mdoc — only format and the request/response payload shape differ.

Create a credential offer

Body

Response201 Created

credential_configuration_ids is derived from the schema's id, suffixed with the format (e.g. <schemaId>_jwt_vc_json) — not from the display name, since names aren't guaranteed unique.

Dereference an offer (wallet-side)

Returns the full credential_offer payload (grants, pre-authorized_code) for a wallet that received only the offer_id/ offer URI.

  • 404 Not Found — offer unknown or expired.

Exchange the pre-authorized code for a token

Body (form-urlencoded)

Key
Value

grant_type

urn:ietf:params:oauth:grant-type:pre-authorized_code

pre-authorized_code

{{pre_auth_code}}

Response200 OK

  • 400 Bad Request — code already used or invalid ("bad or used code").

Sign a proof-of-possession JWT (wallet stand-in)

Body

This is a test/dev convenience — in production the wallet signs this proof itself with the holder's private key. Returns { "jwt": "..." }.

Request the credential

Body

Response200 OK

  • 401 Unauthorized — missing/invalid bearer token.

Verify an issued credential

Body

Response200 OK, with per-check results, e.g.:


3. OID4VCI — Negative Tests

Documents expected error responses for common failure conditions.

Scenario
Request
Expected result

Replay pre-auth code

POST /oid4vc/token with an already-used pre-authorized_code

400"bad or used code"

Unknown credential_configuration_id

POST /oid4vc/offer with a nonexistent configuration id

404 — not enabled

Unsupported format

POST /oid4vc/offer with a format not enabled on the schema (e.g. requesting vc+sd-jwt on a schema only configured for jwt_vc_json/ldp_vc)

400 — format not supported

Dereference unknown offer

GET /oid4vc/offer/00000000-0000-0000-0000-000000000000

404 — not found or expired

Missing Authorization header

POST /oid4vc/credential without a Bearer token

401 — missing bearer token

Example — unsupported format:

400 Bad Request


4. OID4VP — Presentation Flow

Verifier-initiated presentation flow using DCQL (Digital Credentials Query Language) and the direct_post response mode.

Verifier creates a presentation request

Body

Response

Wallet fetches the request object

Returns a plain JSON payload — not a signed JWT/JAR request object, for compatibility with wallets targeting older OID4VP drafts. Includes:

Sign the VP token (wallet stand-in)

Body

Submit the Verifiable Presentation (direct_post)

Body

Response200 OK, { "status": "ok" }.

Verifier polls the transaction status

Response200 OK


5. OID4VP — Negative Tests

Scenario
Request
Expected result

Unknown state

POST /vp/response with a bogus state

400 — unknown or expired state

Unknown transaction id

GET /vp/status/00000000-0000-0000-0000-000000000000

404 — transaction not found

Replay an already-verified VP

POST /vp/response again with the same state/vp_token

400 — transaction not pending (replay rejected)

Holder-binding mismatch

An impostor DID signs someone else's credential and submits it as a VP

403"holder binding failed"

The holder-binding negative test repeats the full flow from Section 4 (fresh /vp/request, fetch request object) but signs the VP token with an impostor_did instead of holder_did, over the original holder's issued credential — the service must detect the DID mismatch and reject with 403.


6. mso_mdoc — Issuance & Verification

ISO/IEC 18013-5 mobile-document (mDL) format issuance and verification, using an EC P-256 issuer key.

mdoc presentation (OID4VP) requires a wallet to build a CBOR DeviceResponse signed with COSE, which can't be produced in a API endpoint.

Generate an mdoc issuer DID (EC P-256 / JsonWebKey2020)

Body

Response DID's verificationMethod[0].type is JsonWebKey2020 with publicKeyJwk.crv: "P-256".

Create an mso_mdoc schema

Same shape as Section 1, with:

GET {{schema_base}}/credential-schema/oid4vci-configs will list this schema with formats: ["mso_mdoc"] and the mdoc.docType/mdoc.namespace echoed back.

Create an mdoc offer, exchange for token, request credential

Identical to the core issuance flow (/oid4vc/offer/oid4vc/token → sign PoP JWT → /oid4vc/credential), except:

  • format is "mso_mdoc"

  • credential_configuration_ids[0] equals the schema id directly (no format suffix)

  • The returned credential is a base64url-encoded CBOR structure — it contains no . (JWT-style) or ~ (SD-JWT-style) separators.

Verify an issued mdoc credential

Body

Response

Negative test — tampered mdoc

Submitting a corrupted credential (e.g. appending garbage bytes) to /credentials/verify must fail cleanly: either checks[0].proof: "NOK" or a populated errors array — never a silent pass.


7. W3C VC Render Method

Demonstrates the W3C VC Render Method spec: embedding a visual (SVG) rendering template in an issued credential, addressed by content digest.

Create a schema with an inline SVG renderMethod

Issue the credential

Standard offer → token → PoP → /oid4vc/credential flow (Section 2). The decoded JWT payload's vc.renderMethod(or top-level renderMethod) contains:

  • id is a URL hosting the rendered SVG template.

  • digestMultibase is a multibase/multihash (SHA-256, base58btc) digest of the served template bytes.

Fetch the hosted render template & verify its digest

Response200 OK, Content-Type: image/svg+xml, body containing <svg.

The consumer recomputes the multibase/multihash SHA-256 digest of the returned SVG bytes and asserts it equals the credential's digestMultibase — this is how a verifier confirms the rendering template hasn't been tampered with.


8. vc+sd-jwt — Issuance & Verification

IETF SD-JWT VC format (draft-ietf-oauth-sd-jwt-vc) with real selective disclosure.

Create a vc+sd-jwt schema

Note the additional vct field (Verifiable Credential Type), required for SD-JWT VC.

Create offer, exchange token, request credential

Same flow as Section 2 with format: "vc+sd-jwt". The offer body may include selectively-disclosable claims, e.g.:

Response from POST /oid4vc/credential:

The ~-separated suffix segments are the selective disclosures per the SD-JWT format.

Verify the issued SD-JWT credential

Response{ "checks": [{ "proof": "OK" }] }.


Appendix: Collection Variables

The Postman collection uses these variables to pass state between requests. When implementing your own client, model equivalent state:

Variable
Set by
Used by

issuer_did, holder_did, impostor_did

POST /did/generate

Schema author, PoP/VP JWT signing

schema_id, mdoc_schema_id, render_schema_id, sdjwt_schema_id

POST /credential-schema

credential_configuration_id in /oid4vc/offer

offer_id

POST /oid4vc/offer

GET /oid4vc/offer/{offer_id}

pre_auth_code

POST /oid4vc/offer (via credential_offer.grants)

POST /oid4vc/token

access_token, c_nonce

POST /oid4vc/token

POST /oid4vc/credential(Authorization header, PoP JWT nonce)

pop_jwt

POST /utils/sign-jwt

POST /oid4vc/credential(proof.jwt)

issued_credential, mdoc_credential, render_credential, sdjwt_credential

POST /oid4vc/credential

POST /credentials/verify, VP token payload

vp_transaction_id, vp_request_uri

POST /vp/request

GET /vp/status/{id}, wallet fetch of request object

vp_nonce, vp_state

GET {vp_request_uri}

VP JWT payload, POST /vp/response

vp_jwt

POST /utils/sign-jwt

POST /vp/response (vp_token)

render_template_url, render_digest

Decoded from issued render-method credential

GET {render_template_url} digest check

Last updated