DCQL (Digital Credentials Query Language)
DCQL (Digital Credentials Query Language) is the query language for specifying credential requirements in OID4VP 1.0. It provides a structured way for verifiers to express what credentials and claims they need from a holder's wallet.
Overview
DCQL enables verifiers to precisely describe their credential requirements:
- Which credential types and formats are needed
- What specific claims must be disclosed
- Alternative credentials that can satisfy a requirement
- Trusted authorities for issuer validation
Query Structure
A DCQL query has a hierarchical structure:
Basic Query
- Android/Kotlin
- iOS/Swift
import com.sphereon.openid.oid4vp.dcql.*
// Create a DCQL query for an mDL credential
val dcqlQuery = DcqlQuery(
credentials = listOf(
DcqlCredentialQuery(
id = "mDL",
format = "mso_mdoc",
meta = buildJsonObject {
put("doctype_value", "org.iso.18013.5.1.mDL")
},
claims = listOf(
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "family_name")),
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "given_name")),
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "birth_date")),
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "portrait"))
)
)
)
)
import SphereonOid4vp
// Create a DCQL query for an mDL credential
let dcqlQuery = DcqlQuery(
credentials: [
DcqlCredentialQuery(
id: "mDL",
format: "mso_mdoc",
meta: ["doctype_value": "org.iso.18013.5.1.mDL"],
claims: [
DcqlClaimQuery(path: ["org.iso.18013.5.1", "family_name"]),
DcqlClaimQuery(path: ["org.iso.18013.5.1", "given_name"]),
DcqlClaimQuery(path: ["org.iso.18013.5.1", "birth_date"]),
DcqlClaimQuery(path: ["org.iso.18013.5.1", "portrait"])
]
)
]
)
JSON Representation
The above query serializes to:
{
"credentials": [
{
"id": "mDL",
"format": "mso_mdoc",
"meta": {
"doctype_value": "org.iso.18013.5.1.mDL"
},
"claims": [
{ "path": ["org.iso.18013.5.1", "family_name"] },
{ "path": ["org.iso.18013.5.1", "given_name"] },
{ "path": ["org.iso.18013.5.1", "birth_date"] },
{ "path": ["org.iso.18013.5.1", "portrait"] }
]
}
]
}
Credential Formats
DCQL supports multiple credential formats:
| Format | Description | Meta Fields |
|---|---|---|
mso_mdoc | ISO 18013-5 mobile documents | doctype_value, namespace_values |
dc+sd-jwt | SD-JWT Verifiable Credentials | vct_values, sd_jwt_alg_values, kb_jwt_alg_values |
jwt_vc_json | JWT-encoded W3C VC | type_values, alg_values |
ldp_vc | JSON-LD W3C VC | type_values, proof_type_values |
mDoc Credentials
For ISO 18013-5 mDoc credentials, specify the document type and namespaced claims:
- Android/Kotlin
- iOS/Swift
val mdocQuery = DcqlCredentialQuery(
id = "driving_license",
format = "mso_mdoc",
meta = buildJsonObject {
put("doctype_value", "org.iso.18013.5.1.mDL")
},
claims = listOf(
// Claims use namespace as first path element
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "family_name")),
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "given_name")),
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "birth_date")),
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "issue_date")),
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "expiry_date")),
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "issuing_country")),
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "document_number")),
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "portrait")),
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "driving_privileges")),
// Age attestation claims
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "age_over_18")),
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "age_over_21")),
// Domestic namespace (country-specific)
DcqlClaimQuery(path = listOf("org.iso.18013.5.1.US", "domestic_category"))
)
)
let mdocQuery = DcqlCredentialQuery(
id: "driving_license",
format: "mso_mdoc",
meta: ["doctype_value": "org.iso.18013.5.1.mDL"],
claims: [
// Claims use namespace as first path element
DcqlClaimQuery(path: ["org.iso.18013.5.1", "family_name"]),
DcqlClaimQuery(path: ["org.iso.18013.5.1", "given_name"]),
DcqlClaimQuery(path: ["org.iso.18013.5.1", "birth_date"]),
DcqlClaimQuery(path: ["org.iso.18013.5.1", "issue_date"]),
DcqlClaimQuery(path: ["org.iso.18013.5.1", "expiry_date"]),
DcqlClaimQuery(path: ["org.iso.18013.5.1", "issuing_country"]),
DcqlClaimQuery(path: ["org.iso.18013.5.1", "document_number"]),
DcqlClaimQuery(path: ["org.iso.18013.5.1", "portrait"]),
DcqlClaimQuery(path: ["org.iso.18013.5.1", "driving_privileges"]),
// Age attestation claims
DcqlClaimQuery(path: ["org.iso.18013.5.1", "age_over_18"]),
DcqlClaimQuery(path: ["org.iso.18013.5.1", "age_over_21"]),
// Domestic namespace
DcqlClaimQuery(path: ["org.iso.18013.5.1.US", "domestic_category"])
]
)
SD-JWT Credentials
For SD-JWT Verifiable Credentials, specify the credential type and claim paths:
- Android/Kotlin
- iOS/Swift
val sdJwtQuery = DcqlCredentialQuery(
id = "identity_credential",
format = "dc+sd-jwt",
meta = buildJsonObject {
putJsonArray("vct_values") {
add("https://credentials.example.com/identity")
}
},
claims = listOf(
// Top-level claims
DcqlClaimQuery(path = listOf("family_name")),
DcqlClaimQuery(path = listOf("given_name")),
DcqlClaimQuery(path = listOf("birth_date")),
DcqlClaimQuery(path = listOf("nationalities")),
// Nested claims using path
DcqlClaimQuery(path = listOf("address", "street_address")),
DcqlClaimQuery(path = listOf("address", "locality")),
DcqlClaimQuery(path = listOf("address", "country"))
)
)
let sdJwtQuery = DcqlCredentialQuery(
id: "identity_credential",
format: "dc+sd-jwt",
meta: ["vct_values": ["https://credentials.example.com/identity"]],
claims: [
// Top-level claims
DcqlClaimQuery(path: ["family_name"]),
DcqlClaimQuery(path: ["given_name"]),
DcqlClaimQuery(path: ["birth_date"]),
DcqlClaimQuery(path: ["nationalities"]),
// Nested claims using path
DcqlClaimQuery(path: ["address", "street_address"]),
DcqlClaimQuery(path: ["address", "locality"]),
DcqlClaimQuery(path: ["address", "country"])
]
)
Claim Configuration
Value Constraints
Request claims with specific expected values:
- Android/Kotlin
- iOS/Swift
val claims = listOf(
// Claim must have this exact value
DcqlClaimQuery(
path = listOf("org.iso.18013.5.1", "issuing_country"),
values = listOf(JsonPrimitive("US"))
),
// Claim must be one of these values
DcqlClaimQuery(
path = listOf("org.iso.18013.5.1", "vehicle_category_code"),
values = listOf(JsonPrimitive("A"), JsonPrimitive("B"), JsonPrimitive("C"))
),
// Boolean value constraint
DcqlClaimQuery(
path = listOf("org.iso.18013.5.1", "age_over_21"),
values = listOf(JsonPrimitive(true))
)
)
let claims = [
// Claim must have this exact value
DcqlClaimQuery(
path: ["org.iso.18013.5.1", "issuing_country"],
values: ["US"]
),
// Claim must be one of these values
DcqlClaimQuery(
path: ["org.iso.18013.5.1", "vehicle_category_code"],
values: ["A", "B", "C"]
),
// Boolean value constraint
DcqlClaimQuery(
path: ["org.iso.18013.5.1", "age_over_21"],
values: [true]
)
]
Intent to Retain
Indicate whether the verifier intends to store the claim:
- Android/Kotlin
- iOS/Swift
val claims = listOf(
// Verifier will store this claim
DcqlClaimQuery(
path = listOf("email"),
intent_to_retain = true
),
// Verifier will only use for verification, not store
DcqlClaimQuery(
path = listOf("age_over_21"),
intent_to_retain = false
)
)
let claims = [
// Verifier will store this claim
DcqlClaimQuery(
path: ["email"],
intentToRetain: true
),
// Verifier will only use for verification, not store
DcqlClaimQuery(
path: ["age_over_21"],
intentToRetain: false
)
]
Multiple Credentials
Request multiple credentials in a single query:
- Android/Kotlin
- iOS/Swift
val dcqlQuery = DcqlQuery(
credentials = listOf(
// Request mDL for identity verification
DcqlCredentialQuery(
id = "identity",
format = "mso_mdoc",
meta = buildJsonObject {
put("doctype_value", "org.iso.18013.5.1.mDL")
},
claims = listOf(
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "family_name")),
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "given_name")),
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "portrait"))
)
),
// Request proof of address
DcqlCredentialQuery(
id = "address_proof",
format = "dc+sd-jwt",
meta = buildJsonObject {
putJsonArray("vct_values") { add("AddressCredential") }
},
claims = listOf(
DcqlClaimQuery(path = listOf("street_address")),
DcqlClaimQuery(path = listOf("locality")),
DcqlClaimQuery(path = listOf("postal_code")),
DcqlClaimQuery(path = listOf("country"))
)
),
// Request employment verification
DcqlCredentialQuery(
id = "employment",
format = "dc+sd-jwt",
meta = buildJsonObject {
putJsonArray("vct_values") { add("EmploymentCredential") }
},
claims = listOf(
DcqlClaimQuery(path = listOf("employer_name")),
DcqlClaimQuery(path = listOf("job_title")),
DcqlClaimQuery(path = listOf("employment_status"))
)
)
)
)
let dcqlQuery = DcqlQuery(
credentials: [
// Request mDL for identity verification
DcqlCredentialQuery(
id: "identity",
format: "mso_mdoc",
meta: ["doctype_value": "org.iso.18013.5.1.mDL"],
claims: [
DcqlClaimQuery(path: ["org.iso.18013.5.1", "family_name"]),
DcqlClaimQuery(path: ["org.iso.18013.5.1", "given_name"]),
DcqlClaimQuery(path: ["org.iso.18013.5.1", "portrait"])
]
),
// Request proof of address
DcqlCredentialQuery(
id: "address_proof",
format: "dc+sd-jwt",
meta: ["vct_values": ["AddressCredential"]],
claims: [
DcqlClaimQuery(path: ["street_address"]),
DcqlClaimQuery(path: ["locality"]),
DcqlClaimQuery(path: ["postal_code"]),
DcqlClaimQuery(path: ["country"])
]
),
// Request employment verification
DcqlCredentialQuery(
id: "employment",
format: "dc+sd-jwt",
meta: ["vct_values": ["EmploymentCredential"]],
claims: [
DcqlClaimQuery(path: ["employer_name"]),
DcqlClaimQuery(path: ["job_title"]),
DcqlClaimQuery(path: ["employment_status"])
]
)
]
)
Credential Sets (Alternatives)
When multiple credential types can satisfy the same requirement, use credential sets to define alternatives:
- Android/Kotlin
- iOS/Swift
val dcqlQuery = DcqlQuery(
credentials = listOf(
DcqlCredentialQuery(
id = "mdl_age",
format = "mso_mdoc",
meta = buildJsonObject {
put("doctype_value", "org.iso.18013.5.1.mDL")
},
claims = listOf(
DcqlClaimQuery(path = listOf("org.iso.18013.5.1", "age_over_21"))
)
),
DcqlCredentialQuery(
id = "age_attestation",
format = "dc+sd-jwt",
meta = buildJsonObject {
putJsonArray("vct_values") { add("AgeAttestation") }
},
claims = listOf(
DcqlClaimQuery(path = listOf("age_over_21"))
)
),
DcqlCredentialQuery(
id = "national_id_age",
format = "mso_mdoc",
meta = buildJsonObject {
put("doctype_value", "org.iso.23220.1.nid")
},
claims = listOf(
DcqlClaimQuery(path = listOf("org.iso.23220.1", "age_over_21"))
)
)
),
// Group them as alternatives - holder provides any ONE
credential_sets = listOf(
DcqlCredentialSetQuery(
required = true,
options = listOf(
DcqlCredentialSetOption(credential_ids = listOf("mdl_age")),
DcqlCredentialSetOption(credential_ids = listOf("age_attestation")),
DcqlCredentialSetOption(credential_ids = listOf("national_id_age"))
)
)
)
)
let dcqlQuery = DcqlQuery(
credentials: [
DcqlCredentialQuery(
id: "mdl_age",
format: "mso_mdoc",
meta: ["doctype_value": "org.iso.18013.5.1.mDL"],
claims: [
DcqlClaimQuery(path: ["org.iso.18013.5.1", "age_over_21"])
]
),
DcqlCredentialQuery(
id: "age_attestation",
format: "dc+sd-jwt",
meta: ["vct_values": ["AgeAttestation"]],
claims: [
DcqlClaimQuery(path: ["age_over_21"])
]
),
DcqlCredentialQuery(
id: "national_id_age",
format: "mso_mdoc",
meta: ["doctype_value": "org.iso.23220.1.nid"],
claims: [
DcqlClaimQuery(path: ["org.iso.23220.1", "age_over_21"])
]
)
],
// Group them as alternatives - holder provides any ONE
credentialSets: [
DcqlCredentialSetQuery(
required: true,
options: [
DcqlCredentialSetOption(credentialIds: ["mdl_age"]),
DcqlCredentialSetOption(credentialIds: ["age_attestation"]),
DcqlCredentialSetOption(credentialIds: ["national_id_age"])
]
)
]
)
Combined Credentials Per Option
An option can require multiple credentials together:
- Android/Kotlin
- iOS/Swift
// Either present diploma OR (university_id AND transcript together)
val credentialSets = listOf(
DcqlCredentialSetQuery(
required = true,
options = listOf(
// Option 1: Just a diploma
DcqlCredentialSetOption(credential_ids = listOf("diploma")),
// Option 2: University ID AND transcript together
DcqlCredentialSetOption(credential_ids = listOf("university_id", "transcript"))
)
)
)
// Either present diploma OR (university_id AND transcript together)
let credentialSets = [
DcqlCredentialSetQuery(
required: true,
options: [
// Option 1: Just a diploma
DcqlCredentialSetOption(credentialIds: ["diploma"]),
// Option 2: University ID AND transcript together
DcqlCredentialSetOption(credentialIds: ["university_id", "transcript"])
]
)
]
Trusted Authorities
Constrain which issuers are acceptable:
- Android/Kotlin
- iOS/Swift
val credentialQuery = DcqlCredentialQuery(
id = "identity",
format = "dc+sd-jwt",
trusted_authorities = listOf(
// OpenID Federation trust anchor
DcqlTrustedAuthority(
type = DcqlTrustedAuthority.TYPE_OPENID_FEDERATION,
values = listOf("https://federation.example.com")
),
// ETSI Trusted List
DcqlTrustedAuthority(
type = DcqlTrustedAuthority.TYPE_ETSI_TRUSTED_LIST,
values = listOf("https://eidas.europa.eu/TL/EN_TL.xml")
),
// X.509 Authority Key Identifier
DcqlTrustedAuthority(
type = DcqlTrustedAuthority.TYPE_AUTHORITY_KEY_IDENTIFIER,
values = listOf("0a1b2c3d4e5f...")
)
),
claims = listOf(
DcqlClaimQuery(path = listOf("family_name"))
)
)
let credentialQuery = DcqlCredentialQuery(
id: "identity",
format: "dc+sd-jwt",
trustedAuthorities: [
// OpenID Federation trust anchor
DcqlTrustedAuthority(
type: .openidFederation,
values: ["https://federation.example.com"]
),
// ETSI Trusted List
DcqlTrustedAuthority(
type: .etsiTrustedList,
values: ["https://eidas.europa.eu/TL/EN_TL.xml"]
),
// X.509 Authority Key Identifier
DcqlTrustedAuthority(
type: .authorityKeyIdentifier,
values: ["0a1b2c3d4e5f..."]
)
],
claims: [
DcqlClaimQuery(path: ["family_name"])
]
)
Holder Binding
Require cryptographic proof that the presenter controls the credential:
- Android/Kotlin
- iOS/Swift
val credentialQuery = DcqlCredentialQuery(
id = "identity",
format = "dc+sd-jwt",
require_cryptographic_holder_binding = true, // Default is true
claims = listOf(
DcqlClaimQuery(path = listOf("family_name"))
)
)
let credentialQuery = DcqlCredentialQuery(
id: "identity",
format: "dc+sd-jwt",
requireCryptographicHolderBinding: true, // Default is true
claims: [
DcqlClaimQuery(path: ["family_name"])
]
)
Scope-to-DCQL Mapping
The IDK supports mapping OAuth scopes to DCQL queries for simplified request flows:
- Android/Kotlin
- iOS/Swift
import com.sphereon.openid.oid4vp.common.ScopeRegistry
import com.sphereon.openid.oid4vp.common.ScopeDefinition
import com.sphereon.openid.oid4vp.common.ScopeResolver
// Define scope mappings
val registry = ScopeRegistry(
definitions = listOf(
ScopeDefinition(
scopeValue = "com.example.identity",
description = "Basic identity information",
dcqlQuery = DcqlQuery(
credentials = listOf(
DcqlCredentialQuery(
id = "identity",
format = "dc+sd-jwt",
claims = listOf(
DcqlClaimQuery(path = listOf("given_name")),
DcqlClaimQuery(path = listOf("family_name"))
)
)
)
)
),
ScopeDefinition(
scopeValue = "com.example.age_verification",
description = "Age verification",
dcqlQuery = DcqlQuery(
credentials = listOf(
DcqlCredentialQuery(
id = "age",
format = "dc+sd-jwt",
claims = listOf(
DcqlClaimQuery(path = listOf("age_over_18"))
)
)
)
)
)
)
)
// Resolve scopes to DCQL
val resolver = ScopeResolver(registry)
val result = resolver.resolve("com.example.identity com.example.age_verification")
if (result.fullyResolved) {
// Use merged DCQL query
val dcqlQuery = result.dcqlQuery
println("Resolved scopes: ${result.resolvedScopes}")
} else {
println("Unresolved scopes: ${result.unresolvedScopes}")
}
import SphereonOid4vp
// Define scope mappings
let registry = ScopeRegistry(
definitions: [
ScopeDefinition(
scopeValue: "com.example.identity",
description: "Basic identity information",
dcqlQuery: DcqlQuery(
credentials: [
DcqlCredentialQuery(
id: "identity",
format: "dc+sd-jwt",
claims: [
DcqlClaimQuery(path: ["given_name"]),
DcqlClaimQuery(path: ["family_name"])
]
)
]
)
),
ScopeDefinition(
scopeValue: "com.example.age_verification",
description: "Age verification",
dcqlQuery: DcqlQuery(
credentials: [
DcqlCredentialQuery(
id: "age",
format: "dc+sd-jwt",
claims: [
DcqlClaimQuery(path: ["age_over_18"])
]
)
]
)
)
]
)
// Resolve scopes to DCQL
let resolver = ScopeResolver(registry: registry)
let result = resolver.resolve(scopeString: "com.example.identity com.example.age_verification")
if result.fullyResolved {
// Use merged DCQL query
let dcqlQuery = result.dcqlQuery
print("Resolved scopes: \(result.resolvedScopes)")
} else {
print("Unresolved scopes: \(result.unresolvedScopes)")
}
Using DCQL in Authorization Requests
Pass the DCQL query when creating an OID4VP authorization request:
- Android/Kotlin
- iOS/Swift
val rp = session.component.oid4vpRpService
val createResult = rp.createAuthorizationRequest(
CreateAuthorizationRequestArgs(
dcqlQuery = dcqlQuery,
clientId = "https://verifier.example.com",
responseUri = "https://verifier.example.com/callback",
responseMode = ResponseMode.DIRECT_POST,
nonce = generateSecureNonce()
)
)
when (createResult) {
is IdkResult.Success -> {
val request = createResult.value.request
// Build URI for QR code or redirect
}
is IdkResult.Failure -> {
handleError(createResult.error)
}
}
let rp = session.component.oid4vpRpService
let createResult = try await rp.createAuthorizationRequest(
args: CreateAuthorizationRequestArgs(
dcqlQuery: dcqlQuery,
clientId: "https://verifier.example.com",
responseUri: "https://verifier.example.com/callback",
responseMode: .directPost,
nonce: generateSecureNonce()
)
)
switch createResult {
case .success(let value):
let request = value.request
// Build URI for QR code or redirect
case .failure(let error):
handleError(error)
}
Data Types Reference
DcqlQuery
data class DcqlQuery(
val credentials: List<DcqlCredentialQuery>? = null,
val credential_sets: List<DcqlCredentialSetQuery>? = null
)
// At least one of credentials or credential_sets must be present
DcqlCredentialQuery
data class DcqlCredentialQuery(
val id: String,
val format: String? = null,
val meta: JsonObject? = null,
val claims: List<DcqlClaimQuery>? = null,
val claim_sets: List<DcqlClaimSet>? = null,
val require_cryptographic_holder_binding: Boolean = true,
val multiple: Boolean = false,
val trusted_authorities: List<DcqlTrustedAuthority>? = null
)
DcqlClaimQuery
data class DcqlClaimQuery(
val path: List<String>,
val values: List<JsonElement>? = null,
val intent_to_retain: Boolean? = null
)
DcqlCredentialSetQuery
data class DcqlCredentialSetQuery(
val required: Boolean = false,
val options: List<DcqlCredentialSetOption>
)
data class DcqlCredentialSetOption(
val credential_ids: List<String>
)
DcqlTrustedAuthority
data class DcqlTrustedAuthority(
val type: String,
val values: List<String>
) {
companion object {
const val TYPE_AUTHORITY_KEY_IDENTIFIER = "authority_key_identifier"
const val TYPE_ETSI_TRUSTED_LIST = "etsi_trusted_list"
const val TYPE_OPENID_FEDERATION = "openid_federation"
}
}
Best Practices
Request only necessary claims. Minimize data collection by requesting only the claims you actually need. This respects user privacy and simplifies consent.
Use meaningful credential IDs. Choose descriptive IDs that help with debugging and logging, such as identity_verification rather than cred1.
Use intent_to_retain appropriately. Be transparent about whether claims will be stored, helping users make informed consent decisions.
Provide alternatives with credential_sets. When multiple credential types can satisfy a requirement, define alternatives to maximize compatibility with different wallets.
Validate queries. Use the DCQL validation utilities to catch errors before sending requests to wallets.