> 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/direct-credential-signing.md).

# Direct Credential Signing

Direct signing produces a signed credential in a single request, without any wallet involvement. Instead of creating a credential offer and waiting for a holder to claim it, you submit the claims and the issuer immediately returns the signed credential in compact serialized form. This is useful when the credential is bound to a passive subject (a product, a device, an asset) rather than to a wallet that can prove key possession, or when your application already has its own delivery channel for the resulting credential.

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

***

## 1. Direct Signing vs an OpenID4VCI Offer

The platform supports two ways to issue a credential. Choose based on who the subject is and how the credential reaches its holder.

* **OpenID4VCI offer** (see [Issuing Credentials & Interacting with Wallets](/develop/issuer/credential-issuance-flow.md)): the issuer creates a credential offer, the recipient scans a QR code or follows an `openid-credential-offer://` deep link, and a wallet claims the credential. The wallet proves possession of its key, so the issued credential is cryptographically bound to the holder (a `cnf` / holder-binding claim). Use this for credentials issued to people who hold a wallet.
* **Direct signing** (this page): the issuer signs and returns the credential synchronously, with no wallet flow and no proof of possession. The subject is identified only by an optional `subjectDid`. Use this when the subject cannot run a wallet or perform a key-binding proof — for example a Digital Product Passport bound to a product DID — or when you need the raw credential string to store or transmit yourself.

Both paths sign with the same issuer DID and produce the same credential formats. Direct signing simply skips the offer-and-claim handshake.

***

## 2. Sign a Credential

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

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

* **`issuerDid`** (path, required): The DID of the issuer that signs the credential. Signing material is resolved from this DID, so it must be a DID the service controls (a `did:web` or `did:key` created via the Agent / DID endpoints). If the issuer's signing material cannot be resolved, the request fails with HTTP 400 Bad Request.

### Request Body

* **`format`** (string, required): The credential format to produce. One of:
  * `"sd-jwt-vc"` — SD-JWT VC, supporting selective disclosure.
  * `"jwt_vc_json"` — JWT-VC-JSON, a W3C Verifiable Credential serialized as a JWT.
* **`payload`** (object, required): The credential claims. For `sd-jwt-vc`, this object **must** include a `vct` (Verifiable Credential Type) string. For `jwt_vc_json`, an optional `type` field (string or array of strings) sets the credential `type` values.
* **`subjectDid`** (string, optional): The DID of the credential subject. For `sd-jwt-vc` it is written as the `sub` claim; for `jwt_vc_json` it becomes `credentialSubject.id`. This identifies the subject only — it does **not** create holder binding, since direct signing involves no proof of key possession.
* **`disclosureFrame`** (object, optional): Selective-disclosure configuration for SD-JWT. An object with a single key:

  * **`_sd`** (array of strings, required, at least one entry): the claim keys that should be made selectively disclosable.

  `disclosureFrame` is only valid when `format` is `"sd-jwt-vc"`. Supplying it with `jwt_vc_json` is rejected with HTTP 400 Bad Request.

### Response Body

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

* **`credential`** (string): The signed credential in compact serialized form. For SD-JWT this is the issuer-signed JWT followed by `~`-separated disclosures; for JWT-VC-JSON it is a compact JWT.
* **`format`** (string): The claim format of the returned credential — `"dc+sd-jwt"` for an SD-JWT VC, or `"jwt_vc"` for a JWT-VC-JSON. These are the DIF claim-format identifiers, which differ from the `format` value you send in the request (`"sd-jwt-vc"` / `"jwt_vc_json"`).

***

## 3. SD-JWT with Selective Disclosure

For `sd-jwt-vc`, the `payload` carries the claims and `disclosureFrame._sd` lists which of those claims the holder can later disclose individually. Claims not listed in `_sd` are always present in the credential; listed claims are hashed into the SD-JWT so that, at presentation time, the subject can reveal them one by one without exposing the rest.

The service requires a `vct` in the payload and stamps an `iat` (issued-at) timestamp for you; you normally do not include `iat` in the payload (a value you supply there would override the default).

### 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",
    "access_level": "L3"
  },
  "subjectDid": "did:key:z6Mk...",
  "disclosureFrame": {
    "_sd": ["employee_id", "full_name", "department", "access_level"]
  }
}
```

### Example Response

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

In this example, `vct` is fixed and present in every presentation, while `employee_id`, `full_name`, `department`, and `access_level` become selectively disclosable: a verifier asking only for `department` receives just that claim.

If `payload.vct` is missing or empty, the request is rejected with HTTP 400 Bad Request and the message `payload.vct is required for sd-jwt-vc format.`

***

## 4. JWT-VC-JSON

For `jwt_vc_json`, the credential is built as a W3C Verifiable Credential and serialized as a JWT, signed with the issuer DID using EdDSA. The `type` field in `payload` sets the credential `type` array; `VerifiableCredential` is always included automatically. The remaining payload fields become `credentialSubject` claims, and `subjectDid` (if supplied) becomes `credentialSubject.id`. A reserved `id` key inside `payload` is not copied into the subject claims.

`disclosureFrame` is not applicable to this format and must be omitted.

### Example Request

```json
{
  "format": "jwt_vc_json",
  "payload": {
    "type": ["VerifiableCredential", "EmploymentCredential"],
    "employee_id": "EMP-001",
    "full_name": "Ada Lovelace",
    "department": "Engineering"
  },
  "subjectDid": "did:key:z6Mk..."
}
```

### Example Response

```json
{
  "credential": "eyJhbGciOiJFZERTQSJ9.eyJ2YyI6ey...",
  "format": "jwt_vc"
}
```

***

## 5. Errors

The endpoint validates the request body and the signing context, returning HTTP 400 Bad Request in these cases:

* The body fails validation (for example an unknown `format`, a missing `payload`, or a `disclosureFrame._sd` that is not a non-empty array of strings).
* `format` is `"sd-jwt-vc"` but `payload.vct` is missing or empty.
* `format` is `"jwt_vc_json"` but `disclosureFrame` was supplied (`disclosureFrame is only supported for sd-jwt-vc format.`).
* The signing material for the issuer DID cannot be resolved (the DID is unknown to the service or has no usable key).

Errors follow the global error shape: `{ statusCode, message, path, timestamp }`.

***

## Security

Direct signing produces a credential with no holder-binding proof, so the issuer is fully responsible for the correctness of the claims and the subject it names. Protect this endpoint accordingly: it requires a valid OIDC JWT Bearer token, and it should only be called by trusted backend systems that have already validated the data they are signing into a credential.


---

# 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/direct-credential-signing.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.
