For the complete documentation index, see llms.txt. This page is also available as Markdown.

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


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): 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

  • 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

Example Response

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

Example Response


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.

Last updated