> 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.md).

# Issuer

The Issuer Service is a foundational component of the Self-Sovereign Identity (SSI) ecosystem, enabling the creation, issuance, and management of Verifiable Credentials (VCs). A single service instance can register multiple issuers, each bound to its own DID; issuers define credential schemas, sign credentials, and hand them to interoperable wallets using the **OpenID4VCI** protocol over a standards-based HTTP API.

**Key Highlights:**

* **Standards-Based Verifiable Credentials** Issues W3C-aligned credentials in two formats: **SD-JWT VC** (`vc+sd-jwt` / `sd-jwt-vc`), which supports selective disclosure so holders reveal only the claims a verifier needs, and **JWT-VC-JSON** (`jwt-vc-json` / `jwt_vc_json`). Credentials are cryptographically signed with Ed25519 (EdDSA) keys held in the service's internal key-management system.
* **Schema Management and Versioning** Credential schemas are JSON-Schema templates that define the claims a credential carries, ensuring consistent credential structures. Each schema has a name (e.g. `EmployeeBadge`) and a string version (e.g. `1.0`), and a schema together with a format yields a **credential configuration id** of the form `Name@Version:format` (e.g. `EmployeeBadge@1.0:sd-jwt`). New versions can be created without disturbing existing ones, and a schema is assigned to an issuer before it can be offered.
* **OpenID4VCI Credential Offerings** An issuer creates a **credential offer** for one or more credential configurations. The offer is returned as an `openid-credential-offer://` URI and can be rendered as a QR code for the recipient to scan. Offers are claimed by a wallet through the OpenID4VCI **pre-authorized code flow**, with an optional PIN (transaction code) that the recipient must enter to bind the claim to a person who has the PIN.
* **Secure, Standards-Based Wallet Interactions** Wallets retrieve credentials using the OpenID4VCI protocol against the issuer's published metadata, exchanging the pre-authorized code (and PIN, if required) for the signed credential. Every issuance is tracked as an **issuance session** so operators can observe its state.
* **Direct Credential Signing** Alongside the wallet-based flow, the service exposes a direct signing endpoint that returns a signed credential in compact serialized form without a wallet round-trip — useful for server-to-server integrations and testing.
* **Architecture Overview**
  * **Issuer Service Instance** Runs as a standalone server backed by PostgreSQL. Each issuer is registered against an existing DID and exposes its own OpenID4VCI issuer metadata. Issuers share the instance's schema store and database: a schema assigned to one issuer becomes available as credential configurations built from all active schemas.
  * **Decentralized Identifiers** The service creates and serves **`did:web`** documents itself over HTTPS (resolvable at `/:uuid/did.json`) and **`did:key`** identifiers, using Ed25519 keys (see [Terminology and Concepts](/develop/issuer/terminology-and-concepts.md) for how this relates to `did:empe`).
  * **Credential Issuance and Storage** Defines schemas, creates offers, signs credentials, and tracks issuance sessions.
  * **Wallet Interaction Layer** Implements QR code–based offers and the OpenID4VCI pre-authorized code flow (with optional PIN) for secure credential claiming.
  * **Security and Access Controls** All operator endpoints require a valid **OIDC JWT Bearer token** (`Authorization: Bearer <token>`), validated against a JWKS endpoint. HTTPS is required in production.

***

## Issuing a Credential

The typical issuer workflow is: create a schema, assign it to an issuer, create an offer, then let a wallet claim it. All requests below require a valid `Authorization: Bearer <token>` header.

### 1. Create a Schema

**`POST /schemas`**

**Request Body:**

```json
{
  "name": "EmployeeBadge",
  "version": "1.0",
  "description": "Employee access badge credential.",
  "formats": ["sd-jwt", "jwt-vc-json"],
  "schema": {
    "type": "object",
    "properties": {
      "employee_id": { "type": "string" },
      "full_name": { "type": "string" },
      "department": { "type": "string" }
    },
    "required": ["employee_id", "full_name", "department"]
  },
  "vct": "https://issuer.example.com/vct/employee-badge",
  "disclosureFrame": ["employee_id", "full_name", "department"]
}
```

* **name** / **version**: Identify the schema. Both are token-like (letters, digits, `.`, `_`, `-`; no spaces) and together form `EmployeeBadge@1.0`.
* **formats**: Which credential formats this schema supports — any of `sd-jwt` and `jwt-vc-json` (defaults to both).
* **schema**: A JSON-Schema describing the credential claims.
* **vct**: Optional VCT identifier used for SD-JWT credential configurations.
* **jwtVcTypes**: Optional credential `type` values used for JWT-VC-JSON configurations.
* **disclosureFrame**: Optional list of claim keys that are selectively disclosable in SD-JWT.
* **defaultValues** / **display**: Optional default claim values applied during issuance and display metadata for wallet UI.

### 2. Assign the Schema to an Issuer

**`POST /issuers/:issuerDid/schemas`**

**Request Body:**

```json
{ "schemaId": "EmployeeBadge@1.0" }
```

**Response Body:**

```json
{ "status": "assigned" }
```

### 3. Create a Credential Offer

**`POST /issuers/:issuerDid/credential-offers`**

**Request Body:**

```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"
      }
    }
  }
}
```

* **credentialConfigurationIds**: One or more credential configurations to offer, each of the form `Name@Version:format`.
* **requirePin**: When `true`, a PIN (transaction code) is generated and must be entered by the recipient. Defaults to `false`.
* **issuanceMetadata**: Optional per-configuration claim values applied during issuance.

**Response Body:**

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

* **credentialOfferUri**: The `openid-credential-offer://` URI the wallet uses to claim the credential.
* **issuanceSessionId**: Identifier for tracking the issuance session.
* **credentialConfigurationIds**: The credential configurations included in the offer.
* **userPin**: Present only when `requirePin` is `true`.

To obtain a scannable QR code instead of JSON, call **`POST /issuers/:issuerDid/credential-offers/qr-code`** with the same body. It returns a **PNG image** and exposes the offer details in the `X-Credential-Offer-Uri`, `X-Issuance-Session-Id`, and (when a PIN is required) `X-Pin` response headers.

### 4. Track the Issuance Session

**`GET /issuance-sessions/:sessionId`**

Returns the session state and offer details. List all sessions for an issuer with **`GET /issuance-sessions?issuerId=<id>`**, or fetch a QR image for the session's offer URI with **`GET /issuance-sessions/:sessionId/qr-code`**.

***

## Direct Credential Signing

For server-to-server use, a credential can be signed directly without the wallet flow.

**`POST /issuers/:issuerDid/credentials/sign`**

**Request Body:**

```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:z6Mk...",
  "disclosureFrame": { "_sd": ["employee_id", "full_name", "department"] }
}
```

* **format**: `sd-jwt-vc` or `jwt_vc_json`. This is the *input* value; note that the response echoes the underlying claim-format identifier, not this value (see below).
* **payload**: The credential claims. For SD-JWT include a `vct`.
* **subjectDid**: Optional DID of the credential subject.
* **disclosureFrame**: SD-JWT only; lists which claims are selectively disclosable. Supplying it with `jwt_vc_json` is rejected.

**Response Body:**

```json
{
  "credential": "<compact serialized credential>",
  "format": "dc+sd-jwt"
}
```

The response `format` field reports the underlying claim-format identifier of the signed credential, which differs from the `format` value sent in the request. Signing with `sd-jwt-vc` returns `"format": "dc+sd-jwt"`, and signing with `jwt_vc_json` returns `"format": "jwt_vc"`.

***

## Authentication

All operator endpoints are protected. Requests must include a valid OIDC-issued JWT in the `Authorization` header:

```bash
Authorization: Bearer <token>
```

Tokens are validated against a JWKS endpoint (`OIDC_JWKS_URL`). During local development, authentication can be bypassed by setting `AUTH_DISABLED=true`.

<figure><img src="/files/6kaMLsgxIHlHUmsazTgv" alt="Credential-offer QR code in the operator console"><figcaption><p>The generated credential-offer QR code in the operator console</p></figcaption></figure>


---

# 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.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.
