API Guide¶
This comprehensive guide provides practical examples for implementing credential presentation flows using EUDIPLO's API. Learn how to integrate presentation requests into your applications with real-world examples.
API Overview¶
EUDIPLO provides a RESTful API for managing credential presentations:
Endpoint | Method | Purpose |
---|---|---|
/presentation-management |
GET |
List all presentation configurations |
/presentation-management |
POST |
Create or update a presentation configuration |
/presentation-management/{id} |
DELETE |
Delete a presentation configuration |
/presentation-management/request |
POST |
Create a presentation request |
All endpoints require OAuth 2.0 authentication with appropriate scopes.
Configuration Management¶
Create a Presentation Configuration¶
Store a new presentation configuration that defines what credentials to request:
curl -X 'POST' \
'http://localhost:3000/presentation-management' \
-H 'Authorization: Bearer eyJhb...npoNk' \
-H 'Content-Type: application/json' \
-d '{
"id": "employee-verification",
"dcql_query": {
"credentials": [
{
"id": "employee-card",
"format": "dc+sd-jwt",
"meta": {
"vct_values": ["https://your-company.com/credentials/vct/employee"]
},
"claims": [
{
"path": ["employee_id"]
},
{
"path": ["department"]
},
{
"path": ["clearance_level"]
}
]
}
]
},
"registrationCert": {
"body": {
"privacy_policy": "https://your-company.com/privacy-policy",
"purpose": [
{
"locale": "en-US",
"name": "Employee verification for secure facility access"
}
],
"contact": {
"website": "https://your-company.com/contact",
"email": "privacy@your-company.com",
"phone": "+1 555 123 4567"
}
}
},
"webhook": {
"url": "https://access-control.your-company.com/presentation-webhook"
}
}'
Response:
List All Configurations¶
Retrieve all presentation configurations for your tenant:
curl -X 'GET' \
'http://localhost:3000/presentation-management' \
-H 'Authorization: Bearer eyJhb...npoNk'
Response:
[
{
"id": "employee-verification",
"dcql_query": {
"credentials": [
{
"id": "employee-card",
"format": "dc+sd-jwt",
"meta": {
"vct_values": [
"https://your-company.com/credentials/vct/employee"
]
},
"claims": [
{
"path": ["employee_id"]
},
{
"path": ["department"]
}
]
}
]
},
"registrationCert": {
"body": {
"privacy_policy": "https://your-company.com/privacy-policy",
"purpose": [
{
"locale": "en-US",
"name": "Employee verification for secure facility access"
}
]
}
},
"webhook": {
"url": "https://access-control.your-company.com/presentation-webhook"
},
"createdAt": "2024-08-08T10:30:00Z"
}
]
Update Configuration¶
Update an existing configuration by posting with the same ID:
curl -X 'POST' \
'http://localhost:3000/presentation-management' \
-H 'Authorization: Bearer eyJhb...npoNk' \
-H 'Content-Type: application/json' \
-d '{
"id": "employee-verification",
"dcql_query": {
"credentials": [
{
"id": "employee-card",
"format": "dc+sd-jwt",
"meta": {
"vct_values": ["https://your-company.com/credentials/vct/employee"]
},
"claims": [
{
"path": ["employee_id"]
},
{
"path": ["department"]
},
{
"path": ["clearance_level"]
},
{
"path": ["employment_status"]
}
]
}
]
},
"registrationCert": {
"body": {
"privacy_policy": "https://your-company.com/privacy-policy",
"purpose": [
{
"locale": "en-US",
"name": "Enhanced employee verification for secure facility access"
}
],
"contact": {
"email": "privacy@your-company.com"
}
}
}
}'
Delete Configuration¶
Remove a presentation configuration:
curl -X 'DELETE' \
'http://localhost:3000/presentation-management/employee-verification' \
-H 'Authorization: Bearer eyJhb...npoNk'
Response:
Creating Presentation Requests¶
URI Response Type¶
Request a presentation with a URI that can be opened in wallets:
curl -X 'POST' \
'http://localhost:3000/presentation-management/request' \
-H 'Authorization: Bearer eyJhb...npoNk' \
-H 'Content-Type: application/json' \
-d '{
"response_type": "uri",
"requestId": "employee-verification"
}'
Response:
{
"uri": "openid4vp://?request_uri=https://your-domain.com/oid4vp/request/abc123&client_id=your-client",
"session_id": "session-456def"
}
QR Code Response Type¶
Request a presentation with a QR code for easy scanning:
curl -X 'POST' \
'http://localhost:3000/presentation-management/request' \
-H 'Authorization: Bearer eyJhb...npoNk' \
-H 'Content-Type: application/json' \
-d '{
"response_type": "qrcode",
"requestId": "employee-verification"
}'
Response: Binary PNG image data for the QR code
Override Webhook for Single Request¶
Override the configured webhook for a specific presentation request:
curl -X 'POST' \
'http://localhost:3000/presentation-management/request' \
-H 'Authorization: Bearer eyJhb...npoNk' \
-H 'Content-Type: application/json' \
-d '{
"response_type": "uri",
"requestId": "employee-verification",
"webhook": {
"url": "https://special-handler.your-company.com/webhook"
}
}'
Implementation Examples¶
Basic Age Verification¶
Create a simple age verification flow:
# 1. Create age verification configuration
curl -X 'POST' \
'http://localhost:3000/presentation-management' \
-H 'Authorization: Bearer eyJhb...npoNk' \
-H 'Content-Type: application/json' \
-d '{
"id": "age-verification",
"dcql_query": {
"credentials": [
{
"id": "pid",
"format": "dc+sd-jwt",
"meta": {
"vct_values": ["https://your-domain.com/credentials/vct/pid"]
},
"claims": [
{
"path": ["age_over_18"]
}
]
}
]
},
"registrationCert": {
"body": {
"privacy_policy": "https://your-domain.com/privacy-policy",
"purpose": [
{
"locale": "en-US",
"name": "Age verification for restricted content access"
}
],
"contact": {
"email": "privacy@your-domain.com"
}
}
}
}'
# 2. Request age verification presentation
curl -X 'POST' \
'http://localhost:3000/presentation-management/request' \
-H 'Authorization: Bearer eyJhb...npoNk' \
-H 'Content-Type: application/json' \
-d '{
"response_type": "uri",
"requestId": "age-verification"
}'
Professional License Verification¶
Verify professional credentials with multiple claims:
# 1. Create professional license configuration
curl -X 'POST' \
'http://localhost:3000/presentation-management' \
-H 'Authorization: Bearer eyJhb...npoNk' \
-H 'Content-Type: application/json' \
-d '{
"id": "license-verification",
"dcql_query": {
"credentials": [
{
"id": "professional-license",
"format": "dc+sd-jwt",
"meta": {
"vct_values": ["https://licensing-authority.gov/credentials/vct/professional-license"]
},
"claims": [
{
"path": ["license_number"]
},
{
"path": ["license_type"]
},
{
"path": ["holder_name"]
},
{
"path": ["expiration_date"]
},
{
"path": ["issuing_authority"]
}
]
}
]
},
"registrationCert": {
"body": {
"privacy_policy": "https://your-domain.com/privacy-policy",
"purpose": [
{
"locale": "en-US",
"name": "Professional license verification for service authorization"
}
],
"contact": {
"website": "https://your-domain.com/contact",
"email": "compliance@your-domain.com",
"phone": "+1 555 987 6543"
}
}
},
"webhook": {
"url": "https://license-verification.your-domain.com/webhook"
}
}'
# 2. Request license verification
curl -X 'POST' \
'http://localhost:3000/presentation-management/request' \
-H 'Authorization: Bearer eyJhb...npoNk' \
-H 'Content-Type: application/json' \
-d '{
"response_type": "uri",
"requestId": "license-verification"
}'
Multi-Credential Verification¶
Request multiple credential types in a single presentation:
curl -X 'POST' \
'http://localhost:3000/presentation-management' \
-H 'Authorization: Bearer eyJhb...npoNk' \
-H 'Content-Type: application/json' \
-d '{
"id": "comprehensive-verification",
"dcql_query": {
"credentials": [
{
"id": "identity",
"format": "dc+sd-jwt",
"meta": {
"vct_values": ["https://government.gov/credentials/vct/pid"]
},
"claims": [
{
"path": ["given_name"]
},
{
"path": ["family_name"]
}
]
},
{
"id": "employment",
"format": "dc+sd-jwt",
"meta": {
"vct_values": ["https://employer.com/credentials/vct/employee"]
},
"claims": [
{
"path": ["employee_id"]
},
{
"path": ["department"]
}
]
},
{
"id": "certification",
"format": "dc+sd-jwt",
"meta": {
"vct_values": ["https://cert-authority.org/credentials/vct/certification"]
},
"claims": [
{
"path": ["certification_level"]
},
{
"path": ["valid_until"]
}
]
}
]
},
"registrationCert": {
"body": {
"privacy_policy": "https://your-domain.com/privacy-policy",
"purpose": [
{
"locale": "en-US",
"name": "Comprehensive verification for high-security access"
}
],
"contact": {
"email": "security@your-domain.com"
}
}
}
}'
Webhook Integration¶
Webhook Handler Implementation¶
Example webhook handler in Node.js/Express:
const express = require('express');
const app = express();
app.use(express.json());
app.post('/presentation-webhook', (req, res) => {
try {
const { sessionId, requestId, status, verifiedClaims } = req.body;
// Validate the webhook payload
if (!sessionId || !requestId || !verifiedClaims) {
return res.status(400).json({ error: 'Invalid payload' });
}
// Process based on presentation type
switch (requestId) {
case 'employee-verification':
handleEmployeeVerification(verifiedClaims);
break;
case 'age-verification':
handleAgeVerification(verifiedClaims);
break;
case 'license-verification':
handleLicenseVerification(verifiedClaims);
break;
default:
console.log('Unknown request type:', requestId);
}
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
function handleEmployeeVerification(claims) {
const employeeData = claims['employee-card'];
if (employeeData) {
// Grant access based on employee data
grantFacilityAccess(
employeeData.employee_id,
employeeData.clearance_level,
);
}
}
function handleAgeVerification(claims) {
const pidData = claims['pid'];
if (pidData && pidData.age_over_18) {
// Allow access to restricted content
allowRestrictedAccess();
}
}
function handleLicenseVerification(claims) {
const licenseData = claims['professional-license'];
if (licenseData) {
// Verify license is valid and not expired
const isValid = validateLicense(licenseData);
if (isValid) {
authorizeService(licenseData.license_number);
}
}
}
app.listen(3001, () => {
console.log('Webhook server running on port 3001');
});
Webhook Payload Structure¶
EUDIPLO sends the following payload to your webhook:
{
"sessionId": "session-abc123",
"requestId": "employee-verification",
"status": "completed",
"verifiedClaims": {
"employee-card": {
"employee_id": "EMP001234",
"department": "Engineering",
"clearance_level": "Level 3",
"employment_status": "Active"
}
},
"verificationDetails": {
"timestamp": "2024-08-08T10:35:00Z",
"issuer_verified": true,
"signature_valid": true,
"not_revoked": true,
"not_expired": true
}
}
Error Handling¶
Common API Errors¶
Configuration Not Found¶
HTTP/1.1 404 Not Found
{
"error": "configuration_not_found",
"message": "No presentation configuration found with ID 'unknown-config'"
}
Invalid DCQL Query¶
HTTP/1.1 400 Bad Request
{
"error": "invalid_dcql",
"message": "DCQL query structure is invalid",
"details": "Missing required field 'credentials'"
}
Authentication Error¶
HTTP/1.1 401 Unauthorized
{
"error": "invalid_token",
"message": "The access token is invalid or expired"
}
Error Recovery¶
Implement proper error handling in your integration:
async function createPresentationRequest(requestId) {
try {
const response = await fetch('/presentation-management/request', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
response_type: 'uri',
requestId: requestId,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.message}`);
}
return await response.json();
} catch (error) {
console.error('Failed to create presentation request:', error);
// Implement retry logic for transient errors
if (error.message.includes('rate limit')) {
await new Promise((resolve) => setTimeout(resolve, 60000));
return createPresentationRequest(requestId); // Retry
}
throw error;
}
}
Testing¶
Testing with Swagger UI¶
EUDIPLO provides a Swagger UI for API testing:
- Navigate to
http://localhost:3000/api
- Authenticate using OAuth 2.0
- Test presentation configuration endpoints
- Generate QR codes for wallet testing
Integration Testing¶
Example test for presentation configuration:
const { expect } = require('chai');
const request = require('supertest');
describe('Presentation API', () => {
let authToken;
let configId;
beforeEach(async () => {
authToken = await getAuthToken();
configId = 'test-config-' + Date.now();
});
it('should create a presentation configuration', async () => {
const config = {
id: configId,
dcql_query: {
credentials: [
{
id: 'test-credential',
format: 'dc+sd-jwt',
meta: {
vct_values: [
'https://test.com/credentials/vct/test',
],
},
claims: [{ path: ['test_claim'] }],
},
],
},
registrationCert: {
body: {
privacy_policy: 'https://test.com/privacy',
purpose: [
{
locale: 'en-US',
name: 'Test purpose',
},
],
contact: { email: 'test@test.com' },
},
},
};
const response = await request(app)
.post('/presentation-management')
.set('Authorization', `Bearer ${authToken}`)
.send(config)
.expect(201);
expect(response.body.id).to.equal(configId);
});
it('should create a presentation request', async () => {
const response = await request(app)
.post('/presentation-management/request')
.set('Authorization', `Bearer ${authToken}`)
.send({
response_type: 'uri',
requestId: configId,
})
.expect(201);
expect(response.body).to.have.property('uri');
expect(response.body).to.have.property('session_id');
});
});
Best Practices¶
API Design¶
- Use meaningful configuration IDs that describe the verification purpose
- Implement idempotent operations for configuration updates
- Cache configurations to reduce API calls
- Handle rate limits gracefully with exponential backoff
Security¶
- Validate all webhook payloads before processing
- Use HTTPS everywhere for secure communication
- Implement proper error handling without exposing sensitive data
- Log security events for audit and monitoring
Performance¶
- Use webhook endpoints for asynchronous processing
- Implement connection pooling for high-volume scenarios
- Monitor API response times and optimize accordingly
- Cache authentication tokens until expiration