REST API
The EDK exposes the full credential design registry over HTTP through the CredentialDesignHttpAdapter. All endpoints live under the adapter base path /api/v1 and follow REST conventions for collection, item, and action endpoints. The same adapter covers credential designs, issuer designs, verifier designs, render variants, imports, refreshes, resolution, source snapshots, and binary assets.
The adapter is built on the EDK's Universal HTTP Adapter, so the same endpoints run unchanged under Ktor, Spring Boot, AWS Lambda, or any other host that implements the universal adapter contract. Routing, content negotiation, and tenant resolution come from the platform layer; the adapter only contributes the endpoint set.
Endpoint Reference
Credential Designs
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/designs/credentials | Create a credential design |
| GET | /api/v1/designs/credentials | List credential designs (filterable) |
| POST | /api/v1/designs/credentials/find/binding | Find designs by full binding object |
| GET | /api/v1/designs/credentials/bindings/{bindingKey}/{bindingValue} | Find designs by binding key and value |
| POST | /api/v1/designs/credentials/import | Import a design from an external source |
| POST | /api/v1/designs/credentials/resolve | Resolve a design with all layers applied |
| GET | /api/v1/designs/credentials/{designId} | Get a credential design by ID |
| PUT | /api/v1/designs/credentials/{designId} | Update a credential design |
| DELETE | /api/v1/designs/credentials/{designId} | Delete a credential design |
| POST | /api/v1/designs/credentials/{designId}/refresh | Refresh an imported design from its source |
Issuer Designs
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/designs/issuers | Create an issuer design |
| GET | /api/v1/designs/issuers | List issuer designs (filterable) |
| POST | /api/v1/designs/issuers/find/binding | Find issuer designs by full binding |
| GET | /api/v1/designs/issuers/bindings/{bindingKey}/{bindingValue} | Find issuer designs by binding key and value |
| POST | /api/v1/designs/issuers/import | Import an issuer design from an external source |
| POST | /api/v1/designs/issuers/resolve | Resolve an issuer design with all layers applied |
| GET | /api/v1/designs/issuers/{designId} | Get an issuer design by ID |
| PUT | /api/v1/designs/issuers/{designId} | Update an issuer design |
| DELETE | /api/v1/designs/issuers/{designId} | Delete an issuer design |
| POST | /api/v1/designs/issuers/{designId}/refresh | Refresh an imported issuer design from its source |
Verifier Designs
The verifier design endpoints mirror the issuer set exactly, swapping issuers for verifiers in the path. Same methods, same path shapes, same request and response bodies (with verifier-specific record types).
Render Variants
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/designs/render/variants | Create a render variant |
| GET | /api/v1/designs/render/variants | List render variants |
| GET | /api/v1/designs/render/variants/{variantId} | Get a render variant by ID |
| PUT | /api/v1/designs/render/variants/{variantId} | Update a render variant |
| DELETE | /api/v1/designs/render/variants/{variantId} | Delete a render variant |
Source Snapshots
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/designs/snapshots/{snapshotId} | Get the raw fetched payload for an imported design |
| POST | /api/v1/designs/snapshots/{snapshotId}/refresh | Re-fetch a snapshot, using its ETag for conditional GET |
Design Assets
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/designs/credentials/{designId}/assets/{locale}/{assetType} | Download an asset (binary stream) |
| POST | /api/v1/designs/credentials/{designId}/assets/{locale}/{assetType} | Upload an asset (binary stream) |
{locale} is a BCP-47 tag (en, nl, en-GB); {assetType} is one of LOGO, BACKGROUND_IMAGE, SVG_TEMPLATE, PDF_TEMPLATE. Both upload and download use application/octet-stream; the asset's stored Content-Type is returned on download and carried in the request Content-Type header on upload.
OpenAPI tags used across the adapter are Credential Designs, Issuer Designs, Verifier Designs, Render Variants, Design Resolution, Design Snapshots, and Design Assets, which drives grouping in tools like Swagger UI and Redoc.
Request Shapes
Most request bodies are the same data classes the IDK credential design service uses internally. The HTTP adapter only handles transport; the shapes are defined once and shared between the in-process service and the REST adapter.
Create a Credential Design
POST /api/v1/designs/credentials
Content-Type: application/json
{
"bindings": [
{ "key": "VCT", "value": "https://issuer.example.com/identity" },
{ "key": "CREDENTIAL_CONFIGURATION_ID", "value": "IdentityCredential" }
],
"alias": "identity-credential",
"hostingMode": "LOCAL",
"displays": [
{ "locale": "en", "name": "Identity Credential", "description": "Government-issued digital identity" },
{ "locale": "nl", "name": "Identiteitsbewijs", "description": "Door de overheid uitgegeven digitale identiteit" }
],
"claims": [
{
"path": [{ "kind": "Property", "name": "given_name" }],
"labels": [
{ "locale": "en", "label": "First Name" },
{ "locale": "nl", "label": "Voornaam" }
],
"mandatory": true,
"order": 1,
"valueKind": "STRING",
"widgetHint": "TEXT"
}
]
}
Returns 201 Created with the full CredentialDesignRecord (including the server-assigned id, tenantId, createdAt, updatedAt).
List with Filters
GET /api/v1/designs/credentials?entityType=CREDENTIAL&hostingMode=LOCAL&bindingKey=VCT&bindingValue=https%3A%2F%2Fissuer.example.com%2Fidentity&aliasContains=identity
Supported query parameters: entityType, hostingMode, bindingKey, bindingValue, aliasContains, sourceType. All optional; unknown values for enum parameters are silently dropped.
Import from External Source
POST /api/v1/designs/credentials/import
Content-Type: application/json
{
"entityType": "CREDENTIAL",
"bindings": [
{ "key": "CREDENTIAL_CONFIGURATION_ID", "value": "IdentityCredential" }
],
"alias": "identity-credential",
"sourceUrl": "https://issuer.example.com/.well-known/openid-credential-issuer",
"sourceType": "OID4VCI_CREDENTIAL_CONFIGURATION"
}
The sourceType values are the IDK's DesignSourceType enum (OID4VCI_CREDENTIAL_CONFIGURATION, OID4VCI_ISSUER_METADATA, SD_JWT_VCT_METADATA, OCA_BUNDLE, W3C_RENDER_METHOD, JSON_SCHEMA, and so on). The server fetches the URL through the SSRF-protected DesignExternalFetcher, snapshots the response, runs the matching format mapper, and persists both the canonical design and the source snapshot.
Returns 201 Created with the resulting CredentialDesignRecord.
Resolve a Design
POST /api/v1/designs/credentials/resolve
Content-Type: application/json
{
"bindingKey": "VCT",
"bindingValue": "https://issuer.example.com/identity",
"preferredLocales": ["en", "nl"],
"renderTarget": "SIMPLE_CARD",
"externalMetadata": {
"ocaBundle": { "d": "EBundleSaid...", "capture_base": { "...": "..." }, "overlays": [] }
}
}
Returns 200 OK with a ResolvedCredentialDesign: the merged design, the matched render variants, derived render hints, the list of applied layers, the set of locked fields, and an ETag for cache validation. The externalMetadata.ocaBundle field is the inline path described in OCA credential design integration.
Either designId, binding, or bindingKey+bindingValue must be present. The resolution engine then runs the full layered provider pipeline documented in the IDK resolution guide.
Upload an Asset
POST /api/v1/designs/credentials/9a4f.../assets/en/LOGO
Content-Type: image/png
<binary PNG payload>
Returns 201 Created with an AssetReference JSON payload pointing to the stored asset.
Download an Asset
GET /api/v1/designs/credentials/9a4f.../assets/en/LOGO
Returns 200 OK with Content-Type set to whatever the asset was stored as (image/png, image/svg+xml, application/pdf), and the raw bytes in the body.
Tenancy
Every endpoint operates in a single tenant context. The tenant is not read from a client-supplied header. It is resolved by the EDK's Layer 1 tenant pipeline from the validated JWT, the Host header, or the configured default, and is then propagated into the request through an internal header that downstream code reads via requireTenantId(). A wire-side X-Tenant-Id header may be present for observability but never participates in authorization or tenant routing. The implications:
- Calls without a JWT (or with one whose claims do not resolve a tenant) and without a matching host fallback fail at the tenant-resolution stage before reaching the design adapter.
- Every list, find, and get operation is implicitly scoped to the resolved tenant; you cannot read another tenant's designs by passing its ID.
- Writes are likewise scoped: a
POST /designs/credentialslands in the caller's tenant regardless of any tenant-shaped field elsewhere in the payload.
See JWT validation for how the tenant claim is configured.
Authorization
All endpoints flow through the EDK's PolicyCommandExtension, so authorization policies are evaluated for every request. A typical policy restricts writes (create, update, delete, import, refresh, asset uploads) to tenant administrators and allows reads (get, list, find, resolve, asset downloads, snapshot retrieval) for any authenticated principal in the tenant. The action names match the underlying command IDs, so a single policy can cover the entire design API with a few rules.
Response Conventions
Successful responses return 200 OK for reads and updates, 201 Created for resource creation (including imports), 204 No Content for deletes. The body for non-empty responses is always JSON unless the endpoint declares application/octet-stream (asset downloads).
Errors use the EDK's standard IdkError envelope:
{
"code": "ALREADY_EXISTS_ERROR",
"message": "Design with binding (VCT, https://issuer.example.com/identity) already exists"
}
Status code mapping follows the error code:
| Error Code | HTTP Status |
|---|---|
ILLEGAL_ARGUMENT_ERROR | 400 Bad Request |
NOT_FOUND_ERROR | 404 Not Found |
ALREADY_EXISTS_ERROR | 409 Conflict |
POLICY_DENIED | 403 Forbidden |
UNKNOWN_ERROR or any other | 500 Internal Server Error |
The same envelope is used everywhere in the EDK, so any client that already handles IdkError for other endpoints handles design errors without any new code.
Path Specificity and Routing
The adapter resolves overlapping path patterns by specificity. GET /designs/credentials/{designId} and GET /designs/credentials/bindings/{bindingKey}/{bindingValue} both start with /designs/credentials/, but the latter is more specific (one extra literal segment) and wins for GET /designs/credentials/bindings/VCT/.... The same rule lets POST /designs/credentials/import, POST /designs/credentials/resolve, POST /designs/credentials/find/binding, and POST /designs/credentials coexist without ambiguity: the bare collection POST is the least specific and is selected only when no other pattern matches.
Mounting the Adapter
Wiring the design REST surface into a host application is a one-liner once the EDK module is on the classpath. The CredentialDesignHttpAdapterImpl registers itself into the session scope through Metro DI and is collected into the platform's set of HttpAdapter instances, which the Universal HTTP Adapter mounts at the chosen route base path. Include lib-data-store-credential-design-rest-vdx in the server module and the endpoints appear under /api/v1/designs/... automatically. No per-endpoint controller code is needed on the host side.
Under the hood each endpoint is implemented as an HttpEndpointCommandAdapter that wraps the matching IDK ServiceCommand, parses path params, query params and the JSON body, calls the command, and maps the result to an HTTP response. The command IDs are stable (credential-design.rest-credential-designs.create, credential-design.rest-resolution.credential, and so on) and are what the authorization policy and audit log key on. They are not part of the public REST contract; the URL paths above are.