---
title: "Issuing Mobile Driver's Licenses (mDL) via OID4VCI"
description: "Learn how to issue ISO 18013-5 Mobile Driver's License (mDL) credentials using the walt.id Issuer2 API."
stack: "Community Stack (open source) — version 0.21.0"
stack_version: "0.21.0"
stack_comparison: https://docs.walt.id/community-vs-enterprise.md
canonical_url: https://docs.walt.id/community-stack/issuer2/credential-issuance/mdl
generated: 2026-07-20
---
# Issuing Mobile Driver's Licenses (mDL) via OID4VCI

This guide shows you how to issue an [ISO/IEC 18013-5](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) Mobile Driver's License (mDL) using the walt.id Issuer2 API. You'll issue a credential from a ready-made profile in seconds, then see how to define your own.

[**mDL (Mobile Driver's License)**](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md): A digital representation of a driver's license based on the ISO/IEC 18013-5 standard, stored securely on a digital ID wallet.

[**OID4VCI**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md): The protocol used to deliver the credential to a wallet (mDoc issuance follows OID4VCI per ISO 18013-7).

**Note:**

Issuer2 issues credentials from **profiles** — reusable configurations declared ahead of time in a config file, with sensible defaults for the issuer key and credential data. If you're used to the original Issuer (v1), where the key and credential data were posted directly to an issuance endpoint every time, see [Getting Started](https://docs.walt.id/community-stack/issuer2/getting-started.md#core-concepts) for what changed — you can still override a profile's defaults per-offer via [`runtimeOverrides`](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md#runtime-overrides).

## Prerequisites

Before you begin, ensure you have:

- **Issuer2 API running** — Follow the [Setup](https://docs.walt.id/community-stack/issuer2/setup.md) guide
- **A test wallet** — Use the [walt.id Wallet](https://wallet.demo.walt.id/) for testing, or any OID4VCI-compatible wallet with mDoc support

**Note:**

**Local Development:** When running locally via Docker, the API is available at `http://localhost:7002` (standalone) or `http://localhost:7005` (Docker Compose). Use this as your base URL in the examples below.

---

## Quick Start: Issue Using the Default Profile

The waltid-identity repository ships with a ready-made profile called `isoMdl`, bound to the `org.iso.18013.5.1.mDL` credential type. You can issue from it immediately — no configuration file editing required.

### Step 1: Confirm the Profile Is Loaded

**Option: CURL**

**Endpoint:** `GET /issuer2/profiles/{profileId}` | [API Reference](https://issuer2.demo.walt.id/swagger/index.html)

##### Example Request

```bash
curl -X 'GET' \
  'http://localhost:7002/issuer2/profiles/isoMdl' \
  -H 'accept: application/json'
```

---

##### Example Response

```json
{
  "profileId": "isoMdl",
  "name": "ISO 18013-5 Mobile Driving License",
  "credentialConfigurationId": "org.iso.18013.5.1.mDL",
  "issuerKey": {
    "type": "jwk",
    "jwk": {
      "d": "KJ4k3Vcl5Sj9Mfq4rrNXBm2MoPoY3_Ak_PIR_EgsFhQ",
      "crv": "P-256",
      "x": "G0RINBiF-oQUD3d5DGnegQuXenI29JDaMGoMvioKRBM",
      "y": "ed3eFGs2pEtrp7vAZ7BLcbrUtpKkYWAT2JPUQK4lN4E",
      "kty": "EC"
    }
  },
  "credentialData": {
    "org.iso.18013.5.1": {
      "un_distinguishing_sign": "A",
      "biometric_template_signature_sign": null,
      "portrait_capture_date": "2024-12-20",
      "age_over_16": true,
      "age_over_18": true,
      "given_name_national_character": "Anna Maria",
      "given_name": "Anna Maria",
      "birth_place": "Graz",
      "family_name": "Musterfrau",
      "age_birth_year": 1988,
      "age_over_12": true,
      "nationality": "AUT",
      "age_over_13": true,
      "age_over_14": true,
      "weight": 63,
      "issuing_jurisdiction": "AT-9",
      "expiry_date": "2035-01-15",
      "signature_usual_mark": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO7Z0i8AAAAASUVORK5CYII=",
      "biometric_template_finger": null,
      "eye_colour": "brown",
      "resident_postal_code": "1070",
      "sex": 2,
      "age_over_62": false,
      "issuing_country": "AT",
      "hair_colour": "brown",
      "issue_date": "2025-01-15",
      "age_over_60": false,
      "age_in_years": 36,
      "administrative_number": "AT-FS-99817234",
      "age_over_65": false,
      "resident_state": "Wien",
      "resident_city": "Wien",
      "document_number": "DL-AT-2025-00018427",
      "portrait": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO7Z0i8AAAAASUVORK5CYII=",
      "resident_country": "AT",
      "resident_address": "Mariahilfer Straße 120/8",
      "age_over_68": false,
      "driving_privileges": [
        {
          "issue_date": "2006-09-14",
          "vehicle_category_code": "B",
          "expiry_date": "2035-01-15"
        },
        {
          "issue_date": "2004-08-25",
          "vehicle_category_code": "AM",
          "expiry_date": "2035-01-15"
        }
      ],
      "age_over_21": true,
      "height": 170,
      "biometric_template_iris": null,
      "birth_date": "1988-08-25",
      "age_over_25": true,
      "biometric_template_face": null,
      "issuing_authority": "Bundesministerium für Inneres",
      "family_name_national_character": "Musterfrau"
    }
  },
  "idTokenClaimsMapping": {
    "$.family_name": "$.['org.iso.18013.5.1'].family_name",
    "$.given_name": "$.['org.iso.18013.5.1'].given_name"
  },
  "mDocNameSpacesDataMappingConfig": {
    "org.iso.18013.5.1": {
      "entriesConfigMap": {
        "portrait_capture_date": {
          "type": "string",
          "conversionType": "stringToFullDate"
        },
        "driving_privileges": {
          "type": "array",
          "arrayConfig": [
            {
              "type": "object",
              "entriesConfigMap": {
                "issue_date": {
                  "type": "string",
                  "conversionType": "stringToFullDate"
                },
                "expiry_date": {
                  "type": "string",
                  "conversionType": "stringToFullDate"
                }
              }
            },
            {
              "type": "object",
              "entriesConfigMap": {
                "issue_date": {
                  "type": "string",
                  "conversionType": "stringToFullDate"
                },
                "expiry_date": {
                  "type": "string",
                  "conversionType": "stringToFullDate"
                }
              }
            }
          ]
        },
        "issue_date": {
          "type": "string",
          "conversionType": "stringToFullDate"
        },
        "portrait": {
          "type": "string",
          "conversionType": "base64StringToByteString"
        },
        "birth_date": {
          "type": "string",
          "conversionType": "stringToFullDate"
        },
        "expiry_date": {
          "type": "string",
          "conversionType": "stringToFullDate"
        },
        "signature_usual_mark": {
          "type": "string",
          "conversionType": "base64StringToByteString"
        }
      }
    }
  },
  "x5Chain": [
    "-----BEGIN CERTIFICATE-----\nMIIBeTCCAR8CFHrWgrGl5KdefSvRQhR+aoqdf48+MAoGCCqGSM49BAMCMBcxFTATBgNVBAMMDE1ET0MgUk9PVCBDQTAgFw0yNTA1MTQxNDA4MDlaGA8yMDc1MDUwMjE0MDgwOVowZTELMAkGA1UEBhMCQVQxDzANBgNVBAgMBlZpZW5uYTEPMA0GA1UEBwwGVmllbm5hMRAwDgYDVQQKDAd3YWx0LmlkMRAwDgYDVQQLDAd3YWx0LmlkMRAwDgYDVQQDDAd3YWx0LmlzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEG0RINBiF+oQUD3d5DGnegQuXenI29JDaMGoMvioKRBN53d4UazakS2unu8BnsEtxutS2kqRhYBPYk9RAriU3gTAKBggqhkjOPQQDAgNIADBFAiAOMwM7hH7q9Di+mT6qCi4LvB+kH8OxMheIrZ2eRPxtDQIhALHzTxwvN8Udt0Z2Cpo8JBihqacfeXkIxVAO8XkxmXhB\n-----END CERTIFICATE-----"
  ]
}
```

The profile already ships with `x5Chain` (for CBOR signing) and `mDocNameSpacesDataMappingConfig` (to encode dates and images to the correct CBOR types) pre-configured — you don't need to understand every field yet to issue your first credential. We'll come back to them in [Defining Your Own Profile](#defining-your-own-profile) below.

### Step 2: Create a Credential Offer

**Option: CURL**

**Endpoint:** `POST /issuer2/credential-offers` | [API Reference](https://issuer2.demo.walt.id/swagger/index.html)

##### Example Request

```bash
curl -X 'POST' \
  'http://localhost:7002/issuer2/credential-offers' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "profileId": "isoMdl",
    "authMethod": "PRE_AUTHORIZED"
  }'
```

**Body Parameters**

- **profileId**: _String_ (required) - The profile to issue from. Here, the shipped default `isoMdl`.
- **authMethod**: _String_ (required) - The OID4VCI flow used to claim the offer. `PRE_AUTHORIZED` requires no user login and is the simplest way to get started. See [Using the Authorization Code Flow Instead](#using-the-authorization-code-flow-instead) for the alternative.

---

##### Example Response

```json
{
  "offerId": "99825a89-73d0-47f4-80cb-321fea52cd54",
  "profileId": "isoMdl",
  "authMethod": "PRE_AUTHORIZED",
  "expiresAt": 1782910641406,
  "credentialOffer": "openid-credential-offer://?credential_offer_uri=http%3A%2F%2Fissuer-api2%3A7002%2Fopenid4vci%2Fcredential-offer%3Fid%3D99825a89-73d0-47f4-80cb-321fea52cd54"
}
```

**Response Fields**

- **offerId**: _String_ - Unique identifier for this offer.
- **expiresAt**: _Long_ - Unix timestamp (ms) when the offer expires (default: 300 seconds after creation).
- **credentialOffer**: _String_ - The OID4VCI credential offer URL. Turn this into a QR code or deep link for the wallet.

---

## Claiming the Offer

1. **Generate a QR code** from the `credentialOffer` URL, or use it directly as a deep link
2. **Open the [walt.id Wallet](https://wallet.demo.walt.id/)** and log in
3. **Click "Request Credential"** and paste the offer URL into the field below the camera
4. **The wallet resolves the offer**, requests the credential, and stores it

🎉 You've issued a Mobile Driver's License using OID4VCI.

To learn how to accept this credential offer programmatically instead, see [How to Accept mDL Credentials via OID4VCI](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-iso-18013-5-mdl-oid4vci.md).

---

## Defining Your Own Profile

To issue your own mDoc credential type instead of the shipped default, two config files need an entry, and the service needs a restart to pick them up.

### Step 1: Declare the Credential Type

Add an entry to `credential-issuer-metadata.conf`:

```hocon
credentialConfigurations = {
  "org.iso.18013.5.1.mDL.custom" = {
    format = "mso_mdoc"
    scope = "org.iso.18013.5.1.mDL.custom"
    doctype = "org.iso.18013.5.1.mDL"
    cryptographic_binding_methods_supported = ["cose_key"]
    credential_signing_alg_values_supported = [-7, -9]
    proof_types_supported = {
      jwt = {
        proof_signing_alg_values_supported = ["ES256"]
      }
    }
  }
}
```

See [Credential Issuer Metadata](https://docs.walt.id/community-stack/issuer2/configurations/config-files/credential-issuer-metadata.md) for the full field reference.

### Step 2: Define a Profile

Add a matching profile to `issuer2-profiles.conf`. Note that mDoc profiles sign with `x5Chain` instead of `issuerDid`, and need `mDocNameSpacesDataMappingConfig` for any date or binary field (see [why this is needed](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md#why-this-is-needed)):

```hocon
profiles {
  myMdl {
    name = "My mDL"
    credentialConfigurationId = "org.iso.18013.5.1.mDL.custom"
    issuerKey = ${defaultIssuerKey}
    x5Chain = ${defaultIssuerX5chain}
    credentialData = {
      "org.iso.18013.5.1" = {
        family_name = "Doe"
        given_name = "John"
        birth_date = "1990-01-01"
        issue_date = "2024-01-01"
        expiry_date = "2029-01-01"
      }
    }
    mDocNameSpacesDataMappingConfig = {
      "org.iso.18013.5.1" = {
        entriesConfigMap = {
          birth_date  = { type = "string", conversionType = "stringToFullDate" }
          issue_date  = { type = "string", conversionType = "stringToFullDate" }
          expiry_date = { type = "string", conversionType = "stringToFullDate" }
        }
      }
    }
  }
}
```

See [Issuer Profiles Configuration](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md) for every available profile field, including notifications and credential status.

### Step 3: Restart and Verify

**Error:**

Profiles are loaded once, at startup. **Restart the Issuer2 container** after editing either config file, then confirm the new profile is loaded.

```bash
curl -X 'GET' 'http://localhost:7002/issuer2/profiles/myMdl'
```

Then repeat the two steps from [Quick Start](#quick-start-issue-using-the-default-profile) above, substituting your own `profileId`.

---

## Using the Authorization Code Flow Instead

The example above used `PRE_AUTHORIZED` — no user login required. Issuer2 also supports the **Authorization Code Flow**, where the holder authenticates against an external IdP (e.g. Keycloak) before claiming the credential, and the resulting ID token claims are mapped into the credential via `idTokenClaimsMapping`.

This requires registering an external OAuth/OIDC provider first. See [Authorization Code Flow](https://docs.walt.id/community-stack/issuer2/protocol-flows/authorization-code-flow.md) for the full sequence and setup.

## mDoc Data Type Mapping

CBOR requires specific types for dates and binary data (e.g. `portrait`) that JSON can't represent natively. See [mDocNameSpacesDataMappingConfig](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md#mdocnamespacesdatamappingconfig) for the full conversion reference.

---

## Next Steps

- **Verify the credential you just issued** — Try [Verifying mDLs](https://docs.walt.id/community-stack/verifier2/credential-verification/mdl-oid4vp.md) with the walt.id Verifier2 API
- **Populate credentials dynamically** — See [Data Functions](https://docs.walt.id/community-stack/issuer2/data-functions.md) for webhooks, timestamps, and more
- **Track issuance events** — See [Notifications](https://docs.walt.id/community-stack/issuer2/notifications.md) for webhook and SSE options
- **Issue other credential types** — Try [SD-JWT VC](https://docs.walt.id/community-stack/issuer2/credential-issuance/sd-jwt-vc.md) or [W3C VC](https://docs.walt.id/community-stack/issuer2/credential-issuance/w3c-vc.md)
