Skip to main content
Version: v0.25.0 (Latest)

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

MethodPathDescription
POST/api/v1/designs/credentialsCreate a credential design
GET/api/v1/designs/credentialsList credential designs (filterable)
POST/api/v1/designs/credentials/find/bindingFind 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/importImport a design from an external source
POST/api/v1/designs/credentials/resolveResolve 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}/refreshRefresh an imported design from its source

Issuer Designs

MethodPathDescription
POST/api/v1/designs/issuersCreate an issuer design
GET/api/v1/designs/issuersList issuer designs (filterable)
POST/api/v1/designs/issuers/find/bindingFind 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/importImport an issuer design from an external source
POST/api/v1/designs/issuers/resolveResolve 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}/refreshRefresh 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

MethodPathDescription
POST/api/v1/designs/render/variantsCreate a render variant
GET/api/v1/designs/render/variantsList 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

MethodPathDescription
GET/api/v1/designs/snapshots/{snapshotId}Get the raw fetched payload for an imported design
POST/api/v1/designs/snapshots/{snapshotId}/refreshRe-fetch a snapshot, using its ETag for conditional GET

Design Assets

MethodPathDescription
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/credentials lands 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 CodeHTTP Status
ILLEGAL_ARGUMENT_ERROR400 Bad Request
NOT_FOUND_ERROR404 Not Found
ALREADY_EXISTS_ERROR409 Conflict
POLICY_DENIED403 Forbidden
UNKNOWN_ERROR or any other500 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.