> For the complete documentation index, see [llms.txt](https://docs.empe.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.empe.io/getting-started/tutorial/credential-formats-and-selective-disclosure.md).

# Credential Formats & Selective Disclosure

**What We Are Doing:**

* Choosing between the two Verifiable Credential formats the platform issues: **SD-JWT VC** and **JWT-VC-JSON**.
* Understanding **credential configuration ids** of the form `name@version:format`, and how a single schema can expose more than one of them.
* Configuring **selective disclosure** for SD-JWT VC credentials with a `disclosureFrame`, so a holder can reveal individual claims without exposing the rest.

**Why:** The format you issue in determines what a credential looks like on the wire and what a holder can do with it when presenting. JWT-VC-JSON is a familiar W3C Verifiable Credential serialized as a JWT — the whole credential is revealed when presented. SD-JWT VC adds *selective disclosure*: each disclosable claim is individually salted and hashed, so the holder can present only the claims a Verifier asks for (for example, `age` without `first_name` or `last_name`). Picking the right format up front means your schema, your credential offers, and your verification requests all line up.

This page assumes you have already created and assigned a schema (see [Uploading the Credential Schema](/getting-started/tutorial/uploading-schema.md)). The same environment variables apply:

```bash
ISSUER_BASE_URL=https://your-issuer.evdi.app
ISSUER_DID=did:web:your-issuer.evdi.app:8f3c...   # your Issuer's DID
TOKEN=<your OIDC access token>                     # JWT with the admin role
```

Every call below requires a valid OIDC Bearer token.

> **Local development:** with `AUTH_DISABLED=true` you can omit the `Authorization` header — see [Authenticating to the API](/getting-started/tutorial/authentication.md).

## The two credential formats

The Issuer signs credentials in one of two formats. You select the format at the schema level (which formats a schema enables) and again per credential offer (which configuration id you offer).

* **SD-JWT VC** — a Selective Disclosure JWT Verifiable Credential. In schema configuration this format is `sd-jwt`; in the direct-signing API request you send `sd-jwt-vc`. At runtime the platform identifies these credentials with the format token **`dc+sd-jwt`** — that is the value the signing and verification responses carry, and the value DCQL queries use (the IETF media type for the credential is `application/dc+sd-jwt`). Each disclosable claim is salted and hashed individually, so the holder can present a subset of claims. SD-JWT VC credentials carry a **`vct`** (Verifiable Credential Type) value that identifies the credential type.
* **JWT-VC-JSON** — a W3C Verifiable Credential serialized as a JWT. In schema configuration this format is `jwt-vc-json`; in the direct-signing API it is identified as `jwt_vc_json`. The credential's `type` array (for example `["VerifiableCredential", "KYCCredential"]`) describes what it is. When presented, the credential is revealed in full — there is no per-claim selective disclosure.

|                                    | SD-JWT VC                  | JWT-VC-JSON                       |
| ---------------------------------- | -------------------------- | --------------------------------- |
| Schema `formats` value             | `sd-jwt`                   | `jwt-vc-json`                     |
| Signing API request `format` value | `sd-jwt-vc`                | `jwt_vc_json`                     |
| Runtime response `format` token    | `dc+sd-jwt`                | `jwt_vc`                          |
| Type identifier                    | `vct` (a single string)    | `jwtVcTypes` (a `type` array)     |
| Selective disclosure               | Yes, via `disclosureFrame` | No (whole credential is revealed) |

Use **SD-JWT VC** when you want holders to share only specific claims (the most common choice, and the one used throughout this tutorial). Use **JWT-VC-JSON** when you need a classic W3C VC and full-credential disclosure is acceptable.

## Credential configuration ids: `name@version:format`

A schema is identified by its `name` and `version` (for example `KYCCredential@1.0`). A schema combined with a single format produces a **credential configuration id** of the form `name@version:format`. This is the value you reference when creating credential offers and what wallets discover in the Issuer metadata.

If a schema enables both formats, it exposes two configuration ids — one per format. For a `KYCCredential@1.0` schema with `formats: ["sd-jwt", "jwt-vc-json"]`:

* `KYCCredential@1.0:sd-jwt`
* `KYCCredential@1.0:jwt-vc-json`

The `formats` field controls which configuration ids exist:

```json
{
  "name": "KYCCredential",
  "version": "1.0",
  "description": "Know-Your-Customer verification credential.",
  "formats": ["sd-jwt"],
  "schema": {
    "type": "object",
    "properties": {
      "age":        { "type": "number" },
      "first_name": { "type": "string" },
      "last_name":  { "type": "string" }
    },
    "required": ["age", "first_name", "last_name"]
  },
  "vct": "https://your-issuer.evdi.app/vct/kyc-credential",
  "disclosureFrame": ["age", "first_name", "last_name"]
}
```

The schema above enables only `sd-jwt`, so it yields a single configuration id, `KYCCredential@1.0:sd-jwt`. Omitting `formats` enables **both** formats by default, giving you both `KYCCredential@1.0:sd-jwt` and `KYCCredential@1.0:jwt-vc-json`.

A few format-specific schema fields are worth calling out:

* **`vct`** — used for the SD-JWT VC type. It identifies what the credential *is* and is the value a Verifier matches against when requesting SD-JWT credentials.
* **`jwtVcTypes`** — used for JWT-VC-JSON. It populates the credential's `type` array (for example `["VerifiableCredential", "KYCCredential"]`). If omitted, sensible defaults are derived from the schema name.
* **`disclosureFrame`** — used for SD-JWT VC selective disclosure (explained next). It is ignored for JWT-VC-JSON.

## Selective disclosure with `disclosureFrame`

Selective disclosure is the defining feature of SD-JWT VC. When a credential is issued, every claim listed in its disclosure frame is encoded as a separate, salted *disclosure*. The signed credential body only contains the hashes of those disclosures, so the holder's wallet can choose, at presentation time, which disclosures to reveal. Claims not listed in the disclosure frame are always present in the credential and are not individually hide-able.

There are two places a disclosure frame appears, depending on how you issue.

### In a schema (the OpenID4VCI flow)

When you issue through a credential offer, the disclosure frame comes from the schema. Set **`disclosureFrame`** to the list of claim keys that should be selectively disclosable:

```json
{
  "name": "KYCCredential",
  "version": "1.0",
  "formats": ["sd-jwt"],
  "schema": {
    "type": "object",
    "properties": {
      "age":        { "type": "number" },
      "first_name": { "type": "string" },
      "last_name":  { "type": "string" }
    },
    "required": ["age", "first_name", "last_name"]
  },
  "vct": "https://your-issuer.evdi.app/vct/kyc-credential",
  "disclosureFrame": ["age", "first_name", "last_name"]
}
```

With this configuration, a holder who has claimed a `KYCCredential@1.0:sd-jwt` credential can later present, say, only `age` to a Verifier and keep `first_name` and `last_name` hidden. If you leave `disclosureFrame` out, every claim defined in the schema's `properties` becomes selectively disclosable by default.

Once the schema is created and assigned, you issue exactly as in [Issuing Credentials](/getting-started/tutorial/issuing-credentials.md) — there is nothing extra to send per offer, because the disclosure frame is already baked into the configuration. The claim values still travel through `issuanceMetadata.claimsByConfigurationId`:

```json
{
  "credentialConfigurationIds": ["KYCCredential@1.0:sd-jwt"],
  "issuanceMetadata": {
    "claimsByConfigurationId": {
      "KYCCredential@1.0:sd-jwt": {
        "age": 30,
        "first_name": "Ada",
        "last_name": "Lovelace"
      }
    }
  }
}
```

### In a direct signing call

For one-off credentials that do not need a wallet/offer flow, the Issuer can sign a credential directly. Here you provide the disclosure frame inline, in the shape **`{ "_sd": [ ... ] }`**.

**`POST {ISSUER_BASE_URL}/issuers/{issuerDid}/credentials/sign`**

Signs a single credential and returns it in compact serialized form. No issuance session or wallet is involved.

**Request Body** (`application/json`):

* **`format`** (string, required) — `sd-jwt-vc` or `jwt_vc_json`.
* **`payload`** (object, required) — the claims to sign. For SD-JWT VC, include a **`vct`** value.
* **`subjectDid`** (string, optional) — the DID of the credential subject; used as the subject identifier in the credential.
* **`disclosureFrame`** (object, optional) — SD-JWT only. An object `{ "_sd": [ ... ] }` whose `_sd` array lists the claim keys that should be selectively disclosable.

**Response Body:**

* **`credential`** — the signed credential in compact serialized form (for SD-JWT VC, the JWT followed by `~`-separated disclosures).
* **`format`** — the resolved, canonical credential-format token (for an SD-JWT VC, `dc+sd-jwt`). Note this is not identical to the `format` enum you send in the request (`sd-jwt-vc`); for a JWT-VC-JSON credential the response token is `jwt_vc`.

Example request:

```bash
curl -X POST "$ISSUER_BASE_URL/issuers/$ISSUER_DID/credentials/sign" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "format": "sd-jwt-vc",
    "payload": {
      "vct": "https://your-issuer.evdi.app/vct/kyc-credential",
      "age": 30,
      "first_name": "Ada",
      "last_name": "Lovelace"
    },
    "disclosureFrame": { "_sd": ["age", "first_name", "last_name"] }
  }'
```

Example response:

```json
{
  "credential": "eyJhbGciOiJFZERTQSJ9.eyJ2Y3QiOi...~WyJzYWx0IiwiYWdlIiwzMF0~",
  "format": "dc+sd-jwt"
}
```

The trailing `~`-separated segments are the disclosures for `age`, `first_name`, and `last_name`. To sign a JWT-VC-JSON credential instead, set `format` to `jwt_vc_json` and omit `disclosureFrame` — that format does not support per-claim disclosure.

## Inspecting what was disclosed

You can decode any credential string — compact JWT or SD-JWT — with the Verifier's direct verification endpoint to confirm which claims it carries.

**`POST {VERIFIER_BASE_URL}/credentials/verify`**

**Request Body:**

* **`credential`** — the compact JWT or SD-JWT string to check.

**Response Body** (when valid):

* **`isValid`** — `true`.
* **`format`** — the detected, canonical format token (for example `dc+sd-jwt` for an SD-JWT VC, `jwt_vc` for a JWT-VC-JSON credential).
* **`payload`** — the decoded credential payload.
* **`prettyClaims`** — for SD-JWT credentials, all disclosed claims merged into a single readable object. (When invalid, the response is `{ "isValid": false, "error": "..." }`, plus the detected `format` whenever the credential could be parsed.)

```bash
curl -X POST "$VERIFIER_BASE_URL/credentials/verify" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "credential": "eyJhbGciOiJFZERTQSJ9.eyJ2Y3QiOi...~WyJzYWx0Iiw..." }'
```

```json
{
  "isValid": true,
  "format": "dc+sd-jwt",
  "payload": { "vct": "https://your-issuer.evdi.app/vct/kyc-credential", "...": "..." },
  "prettyClaims": {
    "vct": "https://your-issuer.evdi.app/vct/kyc-credential",
    "age": 30,
    "first_name": "Ada",
    "last_name": "Lovelace"
  }
}
```

For an SD-JWT VC, `prettyClaims` reflects exactly the disclosures attached to the credential string you passed in — so it is a quick way to verify that selective disclosure is working as expected.

## Matching formats during verification

The format you issue must match the format your Verifier asks for. When you build an OpenID4VP authorization request (see [Creating a Verification Endpoint](/getting-started/tutorial/verify-endpoint.md)), the credential query targets a specific format and can request individual claim paths — which is what makes selective disclosure usable end to end. For example, as a DCQL query:

```json
{
  "dcqlQuery": {
    "credentials": [
      {
        "id": "kyc_credential",
        "format": "dc+sd-jwt",
        "meta": { "vct_values": ["https://your-issuer.evdi.app/vct/kyc-credential"] },
        "claims": [{ "path": ["age"] }]
      }
    ]
  }
}
```

A Presentation Exchange definition expresses the same targeting with a `vc+sd-jwt` format entry and `limit_disclosure: "required"` — that is the form the tutorial's verification endpoint sends, because it can also constrain claim values (e.g. `age >= 18`). Because the credential was issued as SD-JWT VC with `age` in its disclosure frame, the wallet can answer either request by revealing only `age`. Had the same credential been issued as JWT-VC-JSON, the wallet would have to present the entire credential to satisfy the request.

With formats and selective disclosure understood, you can pick `sd-jwt` for privacy-preserving credentials, scope each credential's `disclosureFrame` to the claims holders should control, and reference the resulting `name@version:format` configuration ids in your offers and verification requests.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.empe.io/getting-started/tutorial/credential-formats-and-selective-disclosure.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
