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
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}}/healthReturns 200 OK when the service is up.
Issuer metadata
GET {{oid4vc_base}}/.well-known/openid-credential-issuerReturns 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 formso_mdocissuer 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:
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.
Response — 201 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
Response — 201 Created
credential_configuration_idsis derived from the schema'sid, 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)
grant_type
urn:ietf:params:oauth:grant-type:pre-authorized_code
pre-authorized_code
{{pre_auth_code}}
Response — 200 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
Response — 200 OK
401 Unauthorized— missing/invalid bearer token.
Verify an issued credential
Body
Response — 200 OK, with per-check results, e.g.:
3. OID4VCI — Negative Tests
Documents expected error responses for common failure conditions.
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
Response — 200 OK, { "status": "ok" }.
Verifier polls the transaction status
Response — 200 OK
5. OID4VP — Negative Tests
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
DeviceResponsesigned 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:
formatis"mso_mdoc"credential_configuration_ids[0]equals the schema id directly (no format suffix)The returned
credentialis 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:
idis a URL hosting the rendered SVG template.digestMultibaseis a multibase/multihash (SHA-256, base58btc) digest of the served template bytes.
Fetch the hosted render template & verify its digest
Response — 200 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:
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