API Key Management
API keys let external systems — most commonly learning platforms and HR integrations — create credential offers on behalf of your organisation without requiring a full operator session.
This page covers creating and managing API keys, the available scopes, and the complete integration pattern for a third-party platform.
When to use API keys
API keys are designed for server-to-server integrations where a backend system needs to programmatically create credential offers. The primary use case is the learning platform integration pattern: when a learner completes a course, the platform's backend calls the issuer API to generate a QR code, which is rendered in the learner's completion page.
API keys are not for:
- Operator login to the admin portal (that uses mwen.io DID auth)
- End-user wallet interactions (those use the OID4VCI protocol directly)
Scopes
| Scope | What it allows |
|---|---|
offers:write | Create new credential offers (POST /api/offers) |
offers:read | Read the status of existing offers (GET /api/offers/:id) |
Most integrations require only offers:write. Grant only the scopes your integration needs.
Creating an API key
From the issuer admin portal:
- Go to Settings → API Keys.
- Click New API Key.
- Enter a label (e.g.
LearnCo Production). - Select the scopes required.
- Click Create.
The API key is displayed once. Copy it immediately — it cannot be retrieved after closing the dialog. API keys have the format:
mk_live_<random>
Store API keys in your backend environment variables or secrets manager. Never expose them in client-side code.
Using an API key to create an offer
Pass the API key as a Bearer token in the Authorization header of POST /api/offers:
curl -X POST https://issuer.yourorg.com/api/offers \
-H "Authorization: Bearer mk_live_<api-key>" \
-H "Content-Type: application/json" \
-d '{
"credentialConfigurationId": "course-completion-v1",
"claims": {
"learner_id": "user-abc-123",
"course_id": "python-fundamentals",
"course_title": "Python Fundamentals",
"completion_date": "2026-03-10",
"score": 88,
"issuer_platform": "LearnCo"
}
}'
Response:
{
"offerId": "offer-xyz-456",
"offerUrl": "openid-credential-offer://?credential_offer=...",
"qrCodeDataUrl": "data:image/png;base64,...",
"credentialLink": "https://issuer.yourorg.com/offers/offer-xyz-456",
"expiresAt": "2026-03-11T12:00:00Z"
}
| Response field | Description |
|---|---|
offerUrl | The openid-credential-offer:// deep link. Pass to the mwen.io wallet directly. |
qrCodeDataUrl | A PNG QR code as a base64 data URL. Embed in an <img> tag. |
credentialLink | A hosted page the user can visit on mobile to open the wallet deep link. |
expiresAt | Offer expiry time. After this, the offer can no longer be claimed. |
Learning platform integration pattern
This is the end-to-end flow for issuing a credential when a learner completes a course:
1. Learner completes course on your platform
2. Your backend calls POST /api/offers with the learner's details
3. The issuer returns offerUrl + qrCodeDataUrl
4. Your completion page renders the QR code (<img src={qrCodeDataUrl}>)
5. The learner opens their mwen.io wallet and scans the QR code
6. The wallet shows the credential offer preview
7. The learner taps Accept
8. The credential is stored in the wallet
TypeScript example
async function issueCompletionCredential(learner: Learner, course: Course) {
const response = await fetch('https://issuer.yourorg.com/api/offers', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MWEN_ISSUER_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
credentialConfigurationId: 'course-completion-v1',
claims: {
learner_id: learner.id,
course_id: course.id,
course_title: course.title,
completion_date: new Date().toISOString().split('T')[0],
score: learner.score,
issuer_platform: 'LearnCo',
},
}),
});
if (!response.ok) {
throw new Error(`Offer creation failed: ${response.status}`);
}
const { qrCodeDataUrl, credentialLink } = await response.json();
return { qrCodeDataUrl, credentialLink };
}
In SaaS mode
In SaaS mode, API calls must include the X-Tenant-ID header to identify which tenant the offer belongs to:
headers: {
'Authorization': `Bearer mk_live_<api-key>`,
'X-Tenant-ID': 'a1b2c3d4-...', // your tenant UUID
'Content-Type': 'application/json',
}
Checking offer status
Use GET /api/offers/:id with an offers:read key to check whether an offer has been claimed:
curl https://issuer.yourorg.com/api/offers/offer-xyz-456 \
-H "Authorization: Bearer mk_live_<api-key>"
Response:
{
"offerId": "offer-xyz-456",
"status": "claimed", // "pending" | "claimed" | "expired"
"claimedAt": "2026-03-10T14:23:00Z"
}
Rotating and revoking API keys
To rotate a key:
- Create a new API key in the admin portal.
- Update your integration to use the new key.
- Delete the old key from the admin portal.
Deleting a key immediately invalidates it. Any in-flight requests using the deleted key will receive a 401 Unauthorized response.
Related pages
- Credential Schemas — available
credentialConfigurationIdvalues and claim definitions - Revocation — revoking credentials after issuance
- Issuer Verticals — learning platform vertical details