Docs
Issuance

API Reference

Complete endpoint reference for the Issuance API

POST /oid4vci/offers

Create a credential offer. Returns a URI that the holder's wallet scans to initiate the issuance flow.

Authentication: Secret API key

Request body

{
  "templateId": "550e8400-e29b-41d4-a716-446655440000",
  "claims": {
    "given_name": "Alice",
    "family_name": "Smith",
    "birth_date": "1990-01-15"
  }
}
FieldTypeRequiredDescription
templateIdstring (UUID)YesCredential template ID
claimsobjectNoClaim values to include in the credential
requireTxCodebooleanNoRequire a transaction code (PIN)
useAuthorizationCodebooleanNoInclude authorization code grant alongside pre-auth
offerSchemestringNoScheme of the returned offer URI: openid-credential-offer (default), haip-vci or eu-eaa-offer (EUDI wallets, ETSI TS 119 472-3)
isDeferredbooleanNoEnable deferred issuance
deferredSecondsnumberNoSeconds until credential is ready (1–86400, default: 30)
requirePresentationbooleanNoRequire a verifiable presentation before issuance
presentationRequirementsobjectNoDCQL or PEX query for the required presentation
dataSourceIdstring (UUID)NoData source for auto-populating claims
dataSourceParamsobjectNoLookup parameters for the data source

Response 200

{
  "offer_id": "abc123",
  "credential_offer": {
    "credential_issuer": "https://api.verifiables.com",
    "credential_configuration_ids": ["eu.europa.ec.eudi.pid.1"],
    "grants": {
      "urn:ietf:params:oauth:grant-type:pre-authorized_code": {
        "pre-authorized_code": "oaKazRN8I0IbtZ0C7JuMn5",
        "tx_code": {
          "input_mode": "numeric",
          "length": 6,
          "description": "Enter the code"
        }
      }
    }
  },
  "credential_offer_uri": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2F...",
  "tx_code": "847291"
}
FieldTypeDescription
offer_idstringUnique offer identifier
credential_offerobjectOID4VCI credential offer object
credential_offer_uristringURI for the wallet (display as QR code or deep link)
tx_codestring | undefinedTransaction code to communicate to the holder out-of-band

POST /templates

Create or update a credential template. Upserts by type: if a template with the same type exists, it is updated.

Authentication: Secret API key

Request body

{
  "type": "eu.europa.ec.eudi.pid.1",
  "format": "mso_mdoc",
  "title": "EU Personal ID",
  "description": "European Union Person Identification Data",
  "claims": [
    { "name": "family_name", "type": "string", "mandatory": true },
    { "name": "given_name", "type": "string", "mandatory": true },
    { "name": "birth_date", "type": "string", "mandatory": true },
    { "name": "age_over_18", "type": "boolean" }
  ]
}
FieldTypeRequiredDescription
typestringYesCredential type identifier (e.g., eu.europa.ec.eudi.pid.1)
format"mso_mdoc" | "dc+sd-jwt"YesCredential format
titlestringNoDisplay name
descriptionstringNoHuman-readable description
claimsClaimDefinition[]NoClaim schema definitions
schemaobjectNoJSON Schema for claim validation
bindingMethodsobjectNoCryptographic binding methods configuration
algorithmsobjectNoSigning algorithm configuration
proofTypesobjectNoProof type requirements

Response 200

Returns the created or updated template object.


GET /templates

List all credential templates.

Authentication: API key (publishable or secret)

Response 200

[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "type": "eu.europa.ec.eudi.pid.1",
    "format": "mso_mdoc",
    "title": "EU Personal ID",
    "description": "European Union Person Identification Data",
    "claims": [
      { "name": "family_name", "type": "string", "mandatory": true }
    ]
  }
]

GET /template/:id

Get a single template by ID.

Authentication: API key (publishable or secret)

Path parameters

ParameterTypeDescription
idstring (UUID)Template ID

Response 200

Returns the template object.


POST /type

Find templates by credential type.

Authentication: API key (publishable or secret)

Request body

{
  "type": "eu.europa.ec.eudi.pid.1"
}

Response 200

Returns an array of matching template objects.


Protocol endpoints

These endpoints are part of the OID4VCI protocol and are called by wallets. You do not call them directly; you only need POST /oid4vci/offers to create offers.

POST /oid4vci/par

Pushed Authorization Request. The wallet sends authorization parameters here instead of to the authorization endpoint directly.

ParameterTypeRequiredDescription
response_typestringYesMust be "code"
client_idstringYesWallet client identifier
code_challengestringYesPKCE S256 code challenge (43–128 chars)
code_challenge_methodstringYesMust be "S256"
scopestringNoRequested credential scope
issuer_statestringNoLinks to a pre-existing offer
redirect_uristringNoRedirect URI for the authorization response
client_assertion_typestringNoWallet attestation type
client_assertionstringNoWallet attestation JWT

Response 201

{
  "request_uri": "urn:ietf:params:oauth:request_uri:abc123",
  "expires_in": 60
}

GET /oid4vci/authorize

Authorization endpoint. The wallet navigates here with the PAR request URI.

ParameterTypeRequiredDescription
request_uristringYesRequest URI from PAR response
action"approve" | "deny"NoUser action (default: "approve")

Returns an authorization code as JSON { "code": "..." } or redirects to redirect_uri?code=...&state=....

POST /oid4vci/token

Token endpoint. Supports two grant types:

Pre-authorized code:

ParameterTypeRequiredDescription
grant_typestringYesurn:ietf:params:oauth:grant-type:pre-authorized_code
pre-authorized_codestringYesCode from the credential offer
tx_codestringConditionalTransaction code if required by the offer

Authorization code (PKCE):

ParameterTypeRequiredDescription
grant_typestringYesauthorization_code
codestringYesAuthorization code from /authorize
code_verifierstringYesPKCE code verifier

Both grant types accept optional client_assertion_type, client_assertion, and DPoP header.

Response 200

{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600
}

token_type is DPoP when the request carries a DPoP proof header, otherwise Bearer.

The token response does not contain a nonce (OID4VCI 1.0). Wallets fetch the proof nonce from POST /oid4vci/nonce before building the credential request.

POST /oid4vci/credential

Credential endpoint. The wallet requests the signed credential using the access token.

FieldTypeRequiredDescription
credential_configuration_idstringNoCredential type ID
format"mso_mdoc" | "dc+sd-jwt"NoCredential format
doctypestringNoDocument type (mDoc)
vctstringNoVerifiable credential type (SD-JWT)
proofobjectNoSingle proof of possession
proof.proof_type"jwt" | "attestation"YesProof type
proof.jwtstringConditionalJWS proof JWT
proof.attestationstringConditionalKey attestation
proofsobjectNoBatch proofs: { "jwt": [...] } or { "attestation": [...] }. One credential is issued per proof, all bound to the same claims

Response 200

{
  "credentials": [
    { "credential": "..." }
  ],
  "c_nonce": "new_nonce_456",
  "c_nonce_expires_in": 300
}

The primary response shape is the credentials array (one entry per proof); wallets should read credentials[0].credential. For backward compatibility with pre-1.0 wallets, a deprecated top-level credential field is also present when exactly one credential is issued (and it is the only field returned for legacy single-proof requests). New integrations must not rely on it.

Unlike the token response, the credential response still includes c_nonce and c_nonce_expires_in for the next proof.

Response 202 (deferred)

{
  "transaction_id": "txn_abc",
  "c_nonce": "nonce_789",
  "c_nonce_expires_in": 300
}

POST /oid4vci/deferred

Check status of a deferred credential.

FieldTypeRequiredDescription
transaction_idstringYesTransaction ID from deferred response

Returns 200 with the credential when ready, or 202 with { "transaction_id", "interval": 5 } if still pending.

POST /oid4vci/nonce

Get a fresh nonce for credential requests. This is where wallets obtain the c_nonce to embed in their proof JWT (the token response no longer carries one).

Response 200

{
  "c_nonce": "fresh_nonce"
}

The response contains only c_nonce. There is no c_nonce_expires_in field; expiry is managed server-side.

Discovery endpoints

MethodPathDescription
GET/.well-known/openid-credential-issuerCredential issuer metadata
GET/.well-known/oauth-authorization-serverOAuth authorization server metadata
GET/oid4vci/offers/:idRetrieve a credential offer by ID
GET/oid4vci/offers/:id/statusGet the offer status, returns { "status": "..." }

Schema objects

ClaimDefinition

{
  "name": "family_name",
  "type": "string",
  "mandatory": true,
  "sd": true
}
FieldTypeRequiredDescription
namestringYesClaim name
typestringYesData type (string, boolean, number, bytes)
mandatorybooleanNoWhether the claim must be present
sdbooleanNoSelectively disclosable (SD-JWT only)