Create credential offers
Start an issuance: your backend creates an offer, EUDIPLO returns an offer URI, and you show it to the wallet as a QR code or link. Which flow to offer is explained in the issuance overview.
Prerequisites: a credential configuration, an authorization server in the issuance configuration, and a client with the issuance:offer role.
Create the offer
curl -X POST "$EUDIPLO_URL/api/issuer/offer" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"response_type": "uri",
"flow": "pre_authorized_code",
"credentialConfigurationIds": ["membership"],
"credentialClaims": {
"membership": { "type": "inline", "claims": { "name": "Max", "member_id": "M-001" } }
},
"offerLifetimeSeconds": 600
}'
EUDIPLO answers 201 with:
{
"session": "a6318799-dff4-4b60-9d1d-58703611bd23",
"uri": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Feudiplo.example.com%2Fissuers%2Fmembership-demo%2Fvci%2Fcredential-offers%2Fa6318799-…"
}
uriis the offer to render as QR code or deep link. The wallet resolves the offer by reference at/issuers/{tenant}/vci/credential-offers/{session}.sessionidentifies the issuance session. Use it to follow the status (GET /api/session/{session}:active, thenfetchedafter the first credential,completedorfailedafter the wallet's notification, orexpired), to revoke credentials and to correlate attribute provider requests.
response_type is required by the schema, but issuance offers always answer with this JSON; use uri. Unknown credential configurations and inline claims that do not match the configuration answer 409, an unknown or disabled authorization_server answers 400.
Choose the flow and authorization server
flow | Use it when | Authorization server |
|---|---|---|
pre_authorized_code | Your backend already knows the user. The offer contains a pre-authorized code; anyone who has the offer can redeem it, so protect it with a transaction code if needed. | Must be the built-in server (default if it is the first enabled entry). |
authorization_code | The user must authenticate or present a credential first. The offer contains issuer_state = the session ID. | Set authorization_server to the id of the entry; without it, the first enabled entry is used. |
Transaction code
For pre-authorized offers, tx_code sets a code the user must type into the wallet; send it on a second channel. EUDIPLO advertises its length and input_mode (numeric if it consists of digits only, otherwise text) and shows tx_code_description as hint. A wrong code answers invalid_grant. After txCodeMaxAttempts failed attempts (issuance configuration, default 5) the pre-authorized code is invalidated, and even the correct code is rejected.
Choose the claim source
credentialClaims sets the claim source per credential configuration of this offer. Keys must appear in credentialConfigurationIds; configurations without an entry use their attribute provider or static defaults.
type | Shape | Use it when |
|---|---|---|
inline | { "type": "inline", "claims": { … } } | The values are known now. They are validated when the offer is created. |
attributeProvider | { "type": "attributeProvider", "attributeProviderId": "…" } | Another attribute provider than the configuration's should answer. |
webhook | { "type": "webhook", "webhook": { "url": "…", "auth": { "type": "none" } } } | A one-off endpoint with the attribute provider contract should answer. |
Priority, identity and validation rules are described in Claims.
Lifetime and expiry
Since 9.0, an offer can expire:
offerLifetimeSecondsin the request, or the defaultofferLifetimeSecondsof the issuance configuration, sets how long the offer can be redeemed. Without both, the offer does not expire until the session is removed by the session retention (SESSION_TTL, 24 hours by default).- After expiry, the offer URI answers
404, the token endpointinvalid_grant("The credential offer has expired"), and authorization requests with itsissuer_stateare rejected. Tokens issued before keep their own lifetimes. - The session's
expiresAtholds the deadline. A maintenance job marks unredeemed offers asexpiredeverySESSION_TIDY_UP_INTERVAL; expiry is enforced at request time regardless. - Pre-authorized codes additionally expire with the session retention time (
SESSION_TTLor the tenant's session settings).
Single use
- The offer URI can be resolved once; later fetches answer
404. SetISSUER_MULTI_CONSUMPTION=trueto allow repeated fetches, for example for wallets that load the offer twice. - The code in the offer is redeemed once, at the token endpoint. A second token request answers
invalid_grant("The credential offer has already been used"). Refresh tokens stay usable. - Create a new offer for every issuance.
Your own reference
reference (optional, up to 255 characters) stores an identifier of your system with the session, for example an order or case ID. It is returned in the session list and detail, sent in the session's webhooks, and you can find the session by it later.
The reference is stored in plaintext, unlike the claims, and stays when sessions are anonymized. Use an opaque identifier, never a name, email address or other personal data.
Restrict clients
A client with allowedIssuanceConfigs can only offer the listed credential configurations; other IDs answer 403. See Tenants and access.
Request fields
| Field | Required | Type / allowed values | Description |
|---|---|---|---|
response_type | yes | string: uri | dc-api | iso-18013-7 | Required by the schema; issuance offers are always returned as JSON with the offer URI. Use `uri`. |
flow | yes | string: authorization_code | pre_authorized_code | OID4VCI grant offered to the wallet. |
tx_code | no | string | Transaction code the user must enter (pre-authorized code flow only). |
tx_code_description | no | string | Hint shown by the wallet when asking for the tx_code. |
credentialConfigurationIds | yes | array of string | Credential configurations offered to the wallet. |
authorization_server | no | string | ID of an enabled entry of the issuance configuration's authorizationServers. Defaults to the first enabled entry. |
credentialClaims | no | object | Claim source per credential configuration ID; keys must appear in credentialConfigurationIds. Overrides the configuration's attribute provider and static defaults. |
webhookEndpointId | no | string | Webhook endpoint that receives the wallet's notification events for this offer. |
offerLifetimeSeconds | no | integer (minimum 1) | Lifetime of this offer in seconds. Overrides offerLifetimeSeconds of the issuance configuration; without both the offer does not expire. |
reference | no | string | Your own reference for this offer, e.g. an order or case id. Stored in plaintext, searchable in the session list, included in webhooks and kept when the session is anonymized. Must not contain personal data. |