> 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/develop/issuer/credential-issuance-flow.md).

# Issuing Credentials and Interacting With Wallets

Issuing a credential means creating a **credential offer** for one or more credential configurations and letting a recipient claim it into their wallet. The platform implements the **OpenID4VCI pre-authorized code flow**: the wallet scans a QR code (or follows an `openid-credential-offer://` deep link), optionally enters a PIN, and the issuer signs and returns the credential in the configured format (SD-JWT VC or JWT-VC-JSON). This page walks through the full flow against the live API.

All endpoints below require a valid Bearer token, sent as `Authorization: Bearer <token>` (see [Authentication](/develop/issuer.md#authentication)).

***

## 1. Prerequisites

Before you can create a credential offer you need:

* **An issuer DID** — a `did:web` or `did:key` created through the agent API and hosted/served by the service.
* **A registered issuer** — bound to that DID (`POST /agent/issuer`).
* **At least one schema assigned to the issuer** — assigning a schema produces one or more **credential configurations**.

A schema combined with a format yields a **credential configuration id** of the form `Name@Version:format`, for example `EmployeeBadge@1.0:sd-jwt`. Credential offers reference these configuration ids. See the schema documentation for how to create and assign schemas.

In the examples below, replace `:issuerDid` with your issuer's DID (e.g. `did:web:issuer.example.com:1f2e...` or `did:key:z6Mk...`).

***

## 2. Create a Credential Offer

```
POST /issuers/:issuerDid/credential-offers
Authorization: Bearer <token>
Content-Type: application/json
```

### Request Body

* **`credentialConfigurationIds`** (array of strings, required): One or more credential configuration ids to include in the offer. At least one is required. *Example:* `["EmployeeBadge@1.0:sd-jwt"]`
* **`requirePin`** (boolean, optional): When `true`, the pre-authorized code is protected by a PIN (transaction code) that the holder must enter in their wallet. Defaults to `false`.
* **`issuanceMetadata`** (object, optional): Metadata passed to the credential mapper. Use `claimsByConfigurationId` to supply the claim values for each configuration in the offer.

**Example Request**

```json
{
  "credentialConfigurationIds": ["EmployeeBadge@1.0:sd-jwt"],
  "requirePin": true,
  "issuanceMetadata": {
    "claimsByConfigurationId": {
      "EmployeeBadge@1.0:sd-jwt": {
        "employee_id": "EMP-001",
        "full_name": "Ada Lovelace",
        "department": "Engineering"
      }
    }
  }
}
```

### Response Body

```json
{
  "credentialOfferUri": "openid-credential-offer://?credential_offer=...",
  "issuanceSessionId": "d07d0df5-0d64-4f1a-9aa7-7e7f9c2a1d70",
  "credentialConfigurationIds": ["EmployeeBadge@1.0:sd-jwt"],
  "userPin": "1234"
}
```

* **`credentialOfferUri`** (string): An `openid-credential-offer://` URI. Render it as a QR code or present it as a deep link so a wallet can start the issuance flow.
* **`issuanceSessionId`** (string): The identifier of the issuance session created for this offer. Use it to track issuance progress (see [Track Issuance Sessions](#5-track-issuance-sessions)).
* **`credentialConfigurationIds`** (array of strings): The credential configuration ids included in the offer.
* **`userPin`** (string, optional): Returned only when `requirePin` is `true`. Deliver this PIN to the holder out-of-band; they must enter it in their wallet to claim the credential.

***

## 3. Generate a QR Code

To render the offer as a scannable image without building your own QR encoder, call the QR-code variant. It accepts the **same request body** as the offer endpoint and returns the QR directly as a PNG.

```
POST /issuers/:issuerDid/credential-offers/qr-code
Authorization: Bearer <token>
Content-Type: application/json
```

The response is an `image/png` body (a 400×400 QR encoding the `credentialOfferUri`). The offer details are returned in response headers:

* **`X-Credential-Offer-Uri`** — the `openid-credential-offer://` URI encoded in the QR.
* **`X-Issuance-Session-Id`** — the issuance session id for this offer.
* **`X-Pin`** — the PIN, present only when `requirePin` was `true`.

You can also regenerate a QR for an existing session at any time:

```
GET /issuance-sessions/:sessionId/qr-code
Authorization: Bearer <token>
```

This returns an `image/png` for the session's offer URI, with the `X-Credential-Offer-Uri` header set.

<figure><img src="/files/6kaMLsgxIHlHUmsazTgv" alt="Rendered credential-offer QR code"><figcaption><p>A rendered credential-offer QR code in the operator console</p></figcaption></figure>

***

## 4. Wallet Claims the Credential

Once the offer is presented, the recipient claims it with an SSI-compatible wallet:

1. The wallet **scans the QR code** or **opens the `openid-credential-offer://` link**.
2. It reads the credential offer and the issuer metadata, then runs the **OpenID4VCI pre-authorized code flow** against the issuer's authorization server.
3. If the offer required a PIN, the wallet **prompts the holder for the PIN** (the value returned as `userPin` / the `X-Pin` header).
4. The issuer **signs the credential** for the requested configuration and returns it to the wallet in the configured format — **SD-JWT VC** or **JWT-VC-JSON**.
5. The wallet **stores the credential** for later presentation to verifiers.

The wallet handles token exchange and credential retrieval internally as part of the OpenID4VCI flow; there is no separate client-side token endpoint to call. From the issuer's side, you only create the offer and (optionally) track the session.

Two things happen automatically at claim time:

* The service injects an **`authorized_user`** claim (`authorizedUser` in the JWT-VC-JSON credential subject) into every issued credential payload, set from the `sub` of the wallet's access token, so each credential records which authenticated user claimed it.
* For custom schemas, the claim values are **validated against the schema**: required claims must be present and each claim must match its declared type. A validation failure aborts issuance and moves the session to the `Error` state.

> The platform also provides a cloud (server-side) wallet. A holder tenant can claim an offer programmatically by posting the offer URI to `POST /holders/:holderId/process` with body `{ "uri": "openid-credential-offer://..." }`. See the holder/wallet documentation for details.

***

## 5. Track Issuance Sessions

Each offer creates an issuance session whose state advances as the wallet completes the flow.

**List sessions for an issuer**

```
GET /issuance-sessions?issuerId=<issuerId>
Authorization: Bearer <token>
```

**Get a single session**

```
GET /issuance-sessions/:sessionId
Authorization: Bearer <token>
```

### Response Body

```json
{
  "id": "d07d0df5-0d64-4f1a-9aa7-7e7f9c2a1d70",
  "issuerId": "did:web:issuer.example.com:1f2e...",
  "state": "OfferCreated",
  "credentialOfferUri": "openid-credential-offer://?credential_offer=...",
  "createdAt": "2024-08-09T12:34:56.789Z",
  "expiresAt": "2024-08-09T13:34:56.789Z",
  "userPin": "1234",
  "issuedCredentials": []
}
```

* **`id`** (string): The session identifier (the `issuanceSessionId` returned at offer creation).
* **`issuerId`** (string): The issuer DID associated with the session.
* **`state`** (string): The current state of the issuance session (e.g. `OfferCreated`, `Completed`).
* **`credentialOfferUri`** (string): The offer URI tied to the session.
* **`createdAt`** / **`expiresAt`** (string): ISO 8601 timestamps for creation and expiry.
* **`userPin`** (string, optional): The PIN, when the session used a PIN-protected pre-authorized code.
* **`errorMessage`** (string, optional): Present only when the session ended in the `Error` state; omitted otherwise.
* **`issuedCredentials`** (array of strings): The credential configuration ids issued so far in this session (e.g. `EmployeeBadge@1.0:sd-jwt`). Empty until credentials are issued; fully populated once the session reaches `Completed`.

***

## 6. Direct Signing (optional)

When you need a signed credential without the offer/wallet flow — for example to embed a credential in another system or to issue server-to-server — sign it directly. The issuer signs the payload with its DID's key and returns the credential in compact serialized form.

```
POST /issuers/:issuerDid/credentials/sign
Authorization: Bearer <token>
Content-Type: application/json
```

### Request Body

* **`format`** (string, required): One of `sd-jwt-vc` or `jwt_vc_json`.
* **`payload`** (object, required): The credential claims. For SD-JWT VC, include a `vct` (verifiable credential type) 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 with an `_sd` array listing the claim keys that should be selectively disclosable.

**Example Request**

```json
{
  "format": "sd-jwt-vc",
  "payload": {
    "vct": "https://issuer.example.com/vct/employee-badge",
    "employee_id": "EMP-001",
    "full_name": "Ada Lovelace",
    "department": "Engineering"
  },
  "subjectDid": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRfGNUg9V8q8",
  "disclosureFrame": {
    "_sd": ["employee_id", "full_name", "department"]
  }
}
```

### Response Body

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

* **`credential`** (string): The signed credential in compact serialized form (a compact JWT for `jwt_vc_json`, or an SD-JWT with appended disclosures for `sd-jwt-vc`).
* **`format`** (string): The canonical credential format identifier of the signed credential. This is **not** the request enum value — it is the format the credential is actually issued in: `dc+sd-jwt` for an SD-JWT VC (request `format: "sd-jwt-vc"`), and `jwt_vc` for a JWT-VC-JSON credential (request `format: "jwt_vc_json"`).

***

That is the whole flow: create an offer, present it as a QR or deep link, and let the wallet claim it — tracking progress through the issuance session. For one-off, non-interactive issuance, use the direct signing endpoint.


---

# 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/develop/issuer/credential-issuance-flow.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.
