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

Channels (L4)

L4 is the channel-rendering layer. It is not only about verifiable credentials: one resolved attribute set can be rendered to many outputs, each authored at /api/attributes/v1/channels/{channel}:

  • a form that captures the attributes,
  • a portal page that displays them,
  • a generated PDF document,
  • an API payload for a downstream system,
  • and a verifiable credential.

Every channel renders the same governed set, so a form, a PDF, and a credential built from one set stay consistent by construction. Because this is an issuer and verifier walkthrough, the steps here author the credential channel (the set-bound VC channel) and the OID4VCI issuance channel; the other channels follow the same pattern against the same set.

Everything built at L1 through L3 converges at L4: the attribute set pins a published profile, which pins a published catalog, and a VC channel pins that set. From one set you can derive multiple credential formats, and an issuance channel composes those formats into an issuable offer configuration.

These layers are authored over REST on this page. VDX presents a guided UI for the same layers; the concepts and the resulting model are identical.

Two VC channels (Employee SD-JWT and Business Card mdoc) derived from the same attribute set and the same branding: both bind the set traversal entries to their respective wire formats, with claim paths mapping set roleRef+path pairs to SD-JWT claims or mdoc namespace-element pairs.

The Set-Bound VC Channel

A VC channel binds exactly one attribute set to one credential format. It is the pivot of the issuance and verification flow: it names the credential's claims, its credential-type identity (format + vct or doctype), and the selective-disclosure posture of each claim.

The channel is role-neutral by construction. It does not belong to the issuer or the verifier:

  • The issuer renders a credential design from the channel and issues it.
  • The verifier derives a DCQL credential query from the same channel and requests it.

Because both roles derive from the same channel, the issued credential and the requested credential are guaranteed to agree on attribute identity, selective-disclosure posture, and credential-type identity, by construction. A verifier can also work from a channel for a credential type it does not issue at all: the verifier is a standalone role that exists independently of any issuer.

The key fields on a VC channel are:

FieldMeaning
setRef{ setId, setVersion }. Pins the set version the channel consumes.
credentialFormatdc+sd-jwt or mso_mdoc.
vctThe SD-JWT VC type URI (for dc+sd-jwt).
doctypeThe mdoc doctype string (for mso_mdoc).
brandingRef{ brandingId, version }. References this credential type's L4 branding overlay (its localized display name, description, logo, and colors).
claimMappingsArray of { set: { roleRef, path: [...] }, claimPath?: [...], disclosure }. claimPath is optional and defaults to the set path; provide it only when the wire path differs.

Each claimMappings entry is the through-line from the semantic model to the wire form. set.roleRef and set.path identify the traversal entry in the set; claimPath is the corresponding claim path in the credential wire format; disclosure is "ALWAYS" or "SELECTIVE".

When given, an SD-JWT claimPath is the nested JSON location: ["address", "country"] produces a nested address.country claim. For mdoc it is just the element (["postal_code"]); the channel's namespace field places it, so the namespace is never a path segment. claimPath is omitted whenever it equals the set path, and no claimPath segment ever contains a dot.

VC channel creates return a ChannelEnvelope: { channelType: "VERIFIABLE_CREDENTIAL", channel: { id, version, ... } }. The channelType discriminator tells you which channel kind it is, and the channel id is at .channel.id, not at the top level of the response.

The OID4VCI Issuance Channel

The OID4VCI issuance channel composes one or more VC channels into the credential configurations the issuer advertises. It does not reference the set directly; each VC channel already declares its set binding. Each configuration is independent and sets its own signing algorithm, key-binding method, and offer/grant defaults (shown below).

FieldMeaning
nameA label for the issuance bundle.
credentialConfigurationsOne entry per composed credential, each independent: { credentialConfigurationId, channelRef: { channelId, channelVersion }, signing: { algorithm, keyBinding }, offer: { grants, txCodeRequired, validitySeconds } }. signing.algorithm is e.g. ES256 or EdDSA; signing.keyBinding is a DID method (did:jwk) or x5c.

An OID4VCI channel create returns a ChannelEnvelope with channelType: "OID4VCI". This is the artifact the OID4VCI offer is built against in Issuing.

Branding: the L4 Presentation Overlay

Branding is a separate L4 artifact that carries visual identity for the credential surface. It is entirely separate from the per-attribute overlays.i18n labels in the catalog: attribute labels describe what an attribute means; branding describes how the whole credential looks when rendered on screen.

A branding artifact is the credential's presentation overlay. Per language its locales carry the credential's display name and description plus the logoUri, backgroundColor, and textColor for the wallet card, under an optional layout. The localized display is authored here, once, and a design never re-states it.

Branding is authored per credential type and referenced by each VC channel of that type via brandingRef. A credential issued in several formats (the same credential as both SD-JWT and mdoc) shares one branding; two different credential types (Employee, Business Card) each get their own. So this walkthrough creates two branding records.

Because branding is authored before the VC channels that reference it, the publish step comes first.

Every call on this page

Every write on this page uses the operator's OIDC bearer token and is one of the license-gated enterprise writes. The tenant is resolved from the token's tenant_id claim.

Authorization: Bearer <operator access token>
Content-Type: application/json

With the unbounded default license the gate passes. See the license gate.

Step 1: Create and Publish Branding (one per credential type)

Each locale's name is the credential's display name and description its card description; the visual fields (logoUri, backgroundColor, textColor) are the shared theme.

POST /api/attributes/v1/branding
{
"name": "Employee Credential branding",
"description": "Presentation overlay for the Acme Employee credential, in every format.",
"layout": "card-default",
"locales": [
{ "language": "en", "name": "Employee Credential", "description": "Acme Corp employee identity credential", "logoUri": "https://acme.example/logo.png", "backgroundColor": "#1A237E", "textColor": "#FFFFFF" },
{ "language": "nl", "name": "Medewerkerscredential", "description": "Acme Corp medewerkers identiteitscredential", "logoUri": "https://acme.example/logo.png", "backgroundColor": "#1A237E", "textColor": "#FFFFFF" },
{ "language": "es", "name": "Credencial de empleado", "description": "Credencial de identidad de empleado de Acme Corp", "logoUri": "https://acme.example/logo.png", "backgroundColor": "#1A237E", "textColor": "#FFFFFF" },
{ "language": "fr", "name": "Attestation d'employé", "description": "Attestation d'identité d'employé d'Acme Corp", "logoUri": "https://acme.example/logo.png", "backgroundColor": "#1A237E", "textColor": "#FFFFFF" }
]
}

Create a second branding for the Business Card the same way, with its own display names (Business Card / Visitekaartje / Tarjeta de visita / Carte de visite) and descriptions.

Step 2: Create the Employee SD-JWT VC Channel

The Employee SD-JWT channel binds the published set to the dc+sd-jwt format. It declares 8 claim mappings covering identity, employment status, email, a relationship-traversal address path, and employer name.

Each claim mapping selects a set attribute (roleRef plus its path) and places it on the wire. claimPath is optional: omit it and the wire path defaults to the set path, which is why the identity claims below carry no claimPath at all. Give a claimPath only when the wire path must differ, to rename a claim (the employer's legal_name surfacing as employer) or re-nest it (the traversal ["residentialAddress", "country"] landing under ["address", "country"]). Every path is an array of clean segments; no segment ever contains a dot.

Format-specific structure never goes in the path either. An mdoc element must sit under a namespace, but that namespace is an explicit field on the mdoc channel (namespace), not a path segment; the renderer combines it with the element at issuance.

So the through-line to watch: the set traversal path ["residentialAddress", "country"] maps to the wire claim path ["address", "country"]. The set reaches the value across the relationship boundary; the VC channel decides where it lands on the credential.

POST /api/attributes/v1/channels/credentials
{
"name": "Acme Employee - SD-JWT",
"setRef": { "setId": "7c4d...setId", "setVersion": 1 },
"credentialFormat": "dc+sd-jwt",
"vct": "https://issuer.acme.example/employee",
"brandingRef": { "brandingId": "b1f2...employeeBrandingId", "version": 1 },
"claimMappings": [
{ "set": { "roleRef": "employee", "path": ["given_name"] }, "disclosure": "ALWAYS" },
{ "set": { "roleRef": "employee", "path": ["family_name"] }, "disclosure": "ALWAYS" },
{ "set": { "roleRef": "employee", "path": ["employee_id"] }, "disclosure": "SELECTIVE" },
{ "set": { "roleRef": "employee", "path": ["job_title"] }, "disclosure": "ALWAYS" },
{ "set": { "roleRef": "employee", "path": ["employment_status"] }, "disclosure": "SELECTIVE" },
{ "set": { "roleRef": "employee", "path": ["email"] }, "disclosure": "SELECTIVE" },
{ "set": { "roleRef": "employee", "path": ["residentialAddress", "country"] }, "claimPath": ["address", "country"], "disclosure": "SELECTIVE" },
{ "set": { "roleRef": "employer", "path": ["legal_name"] }, "claimPath": ["employer"], "disclosure": "ALWAYS" }
]
}

Step 3: Create the Business Card mdoc VC Channel

The Business Card mdoc channel binds the same set and the same branding to the mso_mdoc format. Because both channels share the same setRef, given_name means exactly the same thing in both credentials.

mdoc places every element under a namespace. That namespace is declared once as the channel's namespace field (an opaque mdoc identifier, here org.acme.businesscard.1), never inside a path. The direct elements below omit claimPath (it defaults to the set path); only the relationship traversal needs one, to reduce ["residentialAddress", "postal_code"] to the element ["postal_code"]. The renderer combines the channel namespace with each element at issuance.

The second through-line: the set traversal path ["residentialAddress", "postal_code"] crosses the residentialAddress relationship from Person to Address and maps to the mdoc claim path ["postal_code"], which the channel namespaces under org.acme.businesscard.1.

POST /api/attributes/v1/channels/credentials
{
"name": "Acme Business Card - mdoc",
"setRef": { "setId": "7c4d...setId", "setVersion": 1 },
"credentialFormat": "mso_mdoc",
"doctype": "org.acme.businesscard.1",
"namespace": "org.acme.businesscard.1",
"brandingRef": { "brandingId": "b3c4...businessCardBrandingId", "version": 1 },
"claimMappings": [
{ "set": { "roleRef": "employee", "path": ["given_name"] }, "disclosure": "ALWAYS" },
{ "set": { "roleRef": "employee", "path": ["family_name"] }, "disclosure": "ALWAYS" },
{ "set": { "roleRef": "employee", "path": ["job_title"] }, "disclosure": "ALWAYS" },
{ "set": { "roleRef": "employee", "path": ["email"] }, "disclosure": "SELECTIVE" },
{ "set": { "roleRef": "employee", "path": ["residentialAddress", "postal_code"] }, "claimPath": ["postal_code"], "disclosure": "SELECTIVE" }
]
}

Step 4: Create the OID4VCI Issuance Channel

The OID4VCI issuance channel composes the VC channels into credential configurations the issuer advertises in /.well-known/openid-credential-issuer and that offers reference. It carries no setRef; each VC channel already declares its set binding.

Each credential configuration is independent. It picks its VC channel, its credentialConfigurationId, its signing algorithm (for example ES256, or EdDSA for Ed25519) and key-binding method (a DID such as did:jwk, or an X.509 chain via x5c), and its own offer defaults (grant types, tx_code, validity). So one issuance channel can mix pre-authorized and authorization-code credentials, ES256 and Ed25519 keys, and DID-bound and x5c-bound credentials. Here it exposes EmployeeCredential (SD-JWT, ES256, DID, authorization code) and BusinessCardMdoc (mdoc, Ed25519, x5c, pre-authorized), the two ids the offers use in Issuing.

POST /api/attributes/v1/channels/oid4vci
{
"name": "Acme Employee + Business Card issuance",
"credentialConfigurations": [
{
"credentialConfigurationId": "EmployeeCredential",
"channelRef": { "channelId": "9f10...sdjwtChannelId", "channelVersion": 1 },
"signing": { "algorithm": "ES256", "keyBinding": "did:jwk" },
"offer": { "grants": ["authorization_code"], "txCodeRequired": false, "validitySeconds": 600 }
},
{
"credentialConfigurationId": "BusinessCardMdoc",
"channelRef": { "channelId": "a3b4...mdocChannelId", "channelVersion": 1 },
"signing": { "algorithm": "EdDSA", "keyBinding": "x5c" },
"offer": { "grants": ["pre-authorized_code"], "txCodeRequired": true, "validitySeconds": 300 }
}
]
}

The claim structure, selective-disclosure policy, and credential-type identity come from the referenced VC channels. The issuance channel adds, per credential configuration, the signing algorithm, the key-binding method, and the offer defaults, which is what the issuer advertises and what each credential offer references in Issuing.

Listing Channels (the Paged Envelope)

The /api/attributes/v1/channels endpoint lists all channels across channels. You can narrow by channel (VERIFIABLE_CREDENTIAL or OID4VCI) and by setId.

GET /api/attributes/v1/channels?limit=10&offset=0

Filter to VC channels only:

GET /api/attributes/v1/channels?channel=VERIFIABLE_CREDENTIAL&limit=10

Filter by the set they consume:

GET /api/attributes/v1/channels?setId=7c4d...setId&limit=10

The /api/attributes/v1/branding endpoint accepts nameContains and status filters:

GET /api/attributes/v1/branding?status=PUBLISHED&limit=10

What You Have Now

One attribute set now has two VC channels bound to it, each in a different credential format, sharing the same branding. The same canonical attributes underpin both credentials: given_name means exactly the same thing on the Employee SD-JWT and on the Business Card mdoc because both channels trace back to the same Person entity in the same catalog, selected by the same set.

An OID4VCI issuance channel composes both into an issuable configuration. The issuer and verifier both derive from these same two VC channels, so their credential shapes agree by construction without any out-of-band coordination.

Next Steps

  • Issuing: render the two issuer designs from the VC channels and create the credential offer
  • Verifying: author a multi-credential DCQL from the same two channels
  • Attribute Set (L3): the role-scoped traversal subselection that L4 channels consume
  • Modeling Your World: the layer rules and governance model behind these calls