Your First API Call (Credential Factory)
Credential Factory was formerly IaaS (Issuer as a Service). Only the name changed: keep using your existing hostname (for example iaas.stg.iden2.app) and API paths.
This walkthrough takes you from zero to a 201 Created credential offer. You'll create an offer, render the QR code, and watch the offer get claimed.
Prerequisites
- An OAuth access token with the
issuance.credential.createscope (Authentication) - Staging environment access
- A wallet app that supports OID4VCI (to actually claim the credential)
The examples below assume you have already acquired a token and stored it in $ACCESS_TOKEN — see Authentication for the token-acquire step.
1. Create a credential offer
curl -X POST https://iaas.stg.iden2.app/api/v1/iaas/credential-offers \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"authenticationMethod": "PRE_AUTHORIZED",
"credentialConfigurationId": "VerifiedPerson_vc+sd-jwt",
"credentialData": {
"givenName": "Alice",
"familyName": "Smith",
"dateOfBirth": "1990-01-15"
},
"offerTtlSeconds": 300
}'
A successful call returns 201 Created:
{
"offerId": "550e8400-e29b-41d4-a716-446655440000",
"offerUrl": "openid-credential-offer://...?credential_offer=...",
"qrCodeDataUrl": "data:image/png;base64,iVBORw0KGgo...",
"status": "pending",
"createdAt": "2025-01-15T10:00:00Z",
"expiresAt": "2025-01-15T10:05:00Z",
"credentialConfigurationId": "VerifiedPerson_vc+sd-jwt",
"credentialType": "VerifiedPerson"
}
The two fields you care about right now:
qrCodeDataUrl— a ready-to-render PNG QR code. Drop it straight into an<img>tag.offerId— use this to poll the offer status.
2. Show the QR code
<img src="data:image/png;base64,iVBORw0KGgo..." alt="Scan with your wallet to receive your credential" />
That's it — the offerUrl encoded in the QR drives the entire holder flow. See QR Code Rendering for alternatives.
3. Poll for the result
curl https://iaas.stg.iden2.app/api/v1/iaas/credential-offers/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer $ACCESS_TOKEN"
Poll every 3–5 seconds until the credential is issued (claimedAt is present, with status claimed or accepted) or the offer ends (rejected, expired or revoked):
| Status | Meaning | What to do |
|---|---|---|
pending | Waiting for the holder to scan and claim | Keep polling |
claimed | Credential issued to the holder's wallet 🎉 | Done — claimedAt is now present (and holderBinding, when the wallet supplied one) |
accepted | Holder accepted the offer | Done if claimedAt is present; otherwise the credential hasn't been issued yet — keep polling |
rejected | Holder declined the offer (failureReason wallet_rejected) | Stop — create a new offer if they retry |
expired | The offer expired before a credential was issued | Create a new offer; don't retry the same offerId |
revoked | You revoked the credential, or the offer failed (see failureReason) | Stop |
The wallet can accept or reject before or after the credential is issued. An offer accepted before issuance stays accepted, and claimedAt and credentialId are added when the credential is issued.
Troubleshooting
| Response | Likely cause |
|---|---|
401 | Missing, invalid, or expired access token — re-acquire a token (Authentication) |
403 | Your token lacks the required scope, or your configuration isn't permitted to issue this credential type |
404 | The credentialConfigurationId doesn't exist — check the spelling, including the vc+sd-jwt suffix |
400 | Malformed JSON, missing required field, or selectiveDisclosure keys that don't match credentialData keys |
All error bodies follow the shared error model — the detail field tells you exactly what went wrong.
Next steps
- Issuance Integration Guide — production-grade integration with selective disclosure, correlation, and error handling
- SD-JWT & Selective Disclosure — control which fields holders can disclose individually
POST /api/v1/iaas/credential-offersAPI reference — full schema