> For the complete documentation index, see [llms.txt](https://docs.blerify.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.blerify.com/introduction-to-issuance/build/issue-a-w3c-credential.md).

# Issue a W3C Credential

This guide covers issuing a W3C Verifiable Credential from start to finish. The credential moves through the same pipeline regardless of how you drive it — what changes is whether you click through the Portal or call the API. Pick the tab that fits how you work.

{% hint style="info" %}
**Before you start** you need a project with a defined schema — and, for the API path, a service account holding the `credentials.api` role. If that's not set up yet, walk through [Get Started](/introduction-to-issuance/build/get-started.md) first. For who on your team is allowed to create and approve credentials, see [Portal Roles & Permissions](/introduction/portal-roles-and-permissions.md).
{% endhint %}

***

## Issue the credential

{% tabs %}
{% tab title="Portal" %}
No code. Best for setup, testing, and one-off or small-batch issuance run by your operations team.

{% stepper %}
{% step %}

### Create the credential

Open the project you'll issue from — each one carries its own schema and its own list of authorized issuers, so what you can create is already scoped for you. Click **Issue Credential**, fill in the claims form (the fields come straight from your schema), and click **Create**. Blerify saves it as a draft with status `PENDING`. Nothing is signed yet, so you can still review it or discard it.
{% endstep %}

{% step %}

### Approve

Review the draft, and when the data checks out, click **Approve** — this is the moment of issuance. Blerify signs and assembles the credential and moves it to `ISSUED`. From here it's tamper-evident, so any edit would break the signature. If the data is wrong, reject it instead and start over; a rejected credential can't be re-approved.
{% endstep %}

{% step %}

### Deliver to the holder

The Portal offers three ways to hand the credential over:

* **QR code** — show or print it; the holder scans it with their wallet.
* **Deeplink** — send it by email, SMS, or your own channel; tapping it opens the wallet.
* **Push notification** — if the holder already has the Blerify Wallet installed and linked, push the credential straight to them.

The wallet validates the signature on arrival and stores the credential on the holder's device.
{% endstep %}
{% endstepper %}
{% endtab %}

{% tab title="API" %}
Full programmatic control — best for high volume, scheduled batches, or issuance triggered by your own system events. Every request carries a bearer token from your service account (see [Authentication](/introduction/authentication.md)), and the account needs the `credentials.api` role on the target project.

Over the API, issuance is **three chained calls**: create returns a credential `_id` and a `signingMessage`; sign turns that message into a `signature` and a `publicKey`; assemble feeds those back in to produce the finished credential.

{% stepper %}
{% step %}

### Create the credential

The request body ties together the `projectId` and `templateId` that define the credential type, the subject data under `additionalData.w3cData`, and the `organizationUser` who will receive it. Set `options.approvers` to `true` if the credential must be approved by a designated approver before it can be signed.

```bash
curl -X POST \
  "https://api.blerify.com/api/v1/organizations/$ORG/projects/$PROJECT/credentials" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "projectId": "9697f34c-6ba5-4f79-b6dc-0434379d9646",
        "templateId": "6cba8cf5-2d00-4baa-b18a-8797c8c8b550",
        "additionalData": {
          "w3cData": { "email": "ada@example.com", "name": "Ada", "lastname": "Lovelace" }
        },
        "organizationUser": { "email": "ada@example.com" },
        "options": { "approvers": false }
      }'
```

The `201 Created` response includes the draft, trimmed here to the fields that drive the flow:

```json
{
  "_id": "0x412f19b36d6c9a369798415126aec67e...",
  "status": "PENDING",
  "receiver": { "email": "ada@example.com", "repositoryType": "WALLET" },
  "signingMessage": "QS8Zs21smjaXmEFRJq7Gfsx2SmzGkg/xBEAXZpg2Rq8="
}
```

Two values carry the flow forward: `_id` is the credential identifier every later call uses, and `signingMessage` is what the sign step operates on.

Full request and response schema → [API Reference](https://dev.blerify.com/#beac6de4-d5a1-4bcf-bfbb-9cbae49e4bd1)
{% endstep %}

{% step %}

### Sign the credential

Send the `signingMessage` from the previous step in the request body, with the credential `_id` in the path. Blerify performs the cryptographic signing and returns the `signature`, the signing `algorithm`, and the `publicKey` that produced it. Carry the `signature` and `publicKey` into the assemble step.

```bash
curl -X PUT \
  "https://api.blerify.com/api/v1/organizations/$ORG/projects/$PROJECT/credentials/$CREDENTIAL_ID/sign" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "signingMessage": "QS8Zs21smjaXmEFRJq7Gfsx2SmzGkg/xBEAXZpg2Rq8=" }'
```

```json
{
  "signature": "0x1c5d69dcfb39371cb06db1388c815fb...",
  "algorithm": "ecdsa-jcs-2019",
  "publicKey": "0xd7650261601924be00f2abd2958..."
}
```

Full request and response schema → [API Reference](https://dev.blerify.com/#2b542712-0052-44d3-a3bf-1580e1d74e17)
{% endstep %}

{% step %}

### Assemble the credential

This step turns a signature into a credential. Sign proved *who* is vouching for the data; assemble packages that proof into the final W3C Verifiable Credential — the object a wallet can hold and a verifier can check. Send the `templateId` for your project together with the `signature` and `publicKey` from the previous step. Note the `keystore=keyvault` query parameter.

```bash
curl -X PUT \
  "https://api.blerify.com/api/v1/organizations/$ORG/projects/$PROJECT/credentials/$CREDENTIAL_ID/assemble?keystore=keyvault" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "templateId": "6cba8cf5-2d00-4baa-b18a-8797c8c8b550",
        "signature": "0x1c5d69dcfb39371cb06db1388c815fb...",
        "publicKey": "0xd7650261601924be00f2abd2958..."
      }'
```

The response is the complete, signed credential (trimmed here for readability):

```json
{
  "@context": [
    "https://www.w3.org/2018/credentials/v1",
    "https://vc.blerify.com/education/EducationCredential"
  ],
  "type": ["VerifiableCredential", "EducationCredential"],
  "id": "2b94c494-e402-4afd-9bd5-5ffc628efe41",
  "issuer": "did:lac1:1APgL8Z9V4bRQ...",
  "issuanceDate": "2026-02-23T16:14:54.944089Z",
  "credentialSubject": {
    "id": "did:lac1:1iT4pJXCP5YJhP...",
    "subjectName": "Ada Lovelace",
    "title": "Introduction to Cryptography",
    "date": "2026-02-23"
  },
  "proof": [
    {
      "type": "DataIntegrityProof",
      "cryptosuite": "ecdsa-jcs-2019",
      "verificationMethod": "did:lac1:1APgL8Z9V4bRQ...#2orwtrQ29...",
      "proofPurpose": "assertionMethod",
      "proofValue": "zZtk1zBQuzY5qRQWC1sAUd..."
    }
  ]
}
```

Full request and response schema → [API Reference](https://dev.blerify.com/#644f3927-2fa5-452c-8def-133c5e46281b)
{% endstep %}

{% step %}

### Deliver to the holder

Assemble hands you the finished Verifiable Credential — now get it to the holder. Deliver it as a QR code, a deeplink, or a push notification, exactly as the Portal does. The wallet validates the signature on arrival and stores the credential on the holder's device. If a delivery is ever lost, [Manage a Credential](/introduction-to-issuance/build/manage-a-credential.md) covers resending it.
{% endstep %}
{% endstepper %}
{% endtab %}

{% tab title="API (service account, automated)" %}
If your backend issues credentials on behalf of an organization without a human approval step — for example, an LMS issuing credentials automatically when a student completes a course — the flow is a simplified variant: create the credential, approve it directly as the service account, and poll until it is ready.

This requires a service account with the `credentials.api` role. Authentication follows the same `private_key_jwt` pattern described in [Authentication](/introduction/authentication.md).

{% stepper %}
{% step %}

### Create the credential

Send the template, subject data, and receiver identity. The service account creates the draft on the organization's behalf.

```bash
curl -X POST \
  "https://api.blerify.com/api/v1/organizations/$ORG/projects/$PROJECT/credentials" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "templateId": "your-template-id",
        "additionalData": {
          "w3cData": {
            "fullname": "Your Recipient",
            "metadata": { "level": 1 }
          }
        },
        "organizationUser": {
          "email": "recipient@example.com"
        },
        "options": { "omitApproval": false, "approvers": true }
      }'
```

The `201` response includes `_id` — keep it for the steps below.

Full request and response schema → [API Reference](https://dev.blerify.com/#01cf28b2-23f9-472d-a822-ab8cc0ce0114)
{% endstep %}

{% step %}

### Approve (sign) the credential

Approving as a service account records the approval and triggers issuance asynchronously. Include `keystore=keyvault` and a `lang` query parameter for the receiver's language.

```bash
curl -X PUT \
  "https://api.blerify.com/api/v1/organizations/$ORG/projects/$PROJECT/credentials/$CREDENTIAL_ID/sign?keystore=keyvault&lang=en" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "templateId": "your-template-id" }'
```

A `200` response means the approval was recorded and signing was triggered — **not** that the credential is ready. Confirm the result by polling.
{% endstep %}

{% step %}

### Poll until issued

Poll the credential until `status` is `SENT`. The `templateId` query parameter is required — access is scoped to organization, project, and template.

```bash
curl "https://api.blerify.com/api/v1/organizations/$ORG/projects/$PROJECT/credentials/$CREDENTIAL_ID/polling?templateId=$TEMPLATE_ID" \
  -H "Authorization: Bearer $TOKEN"
```

While issuing:

```json
{ "_id": "0x8f3a...c1", "status": "PENDING", "pdf": null, "thumbnail": null, "code": null }
```

When ready:

```json
{
  "_id": "0x8f3a...c1",
  "status": "SENT",
  "pdf": "https://storage.../credential.pdf?signature=...",
  "thumbnail": "https://storage.../thumbnail.png?signature=...",
  "code": "abC12XyZ..."
}
```

| Field              | Use                                                                                                     |
| ------------------ | ------------------------------------------------------------------------------------------------------- |
| `status`           | Poll until `SENT`. The credential moves to `DELIVERED` once the holder claims it.                       |
| `pdf`, `thumbnail` | Short-lived signed URLs. Fetch or render them promptly after polling, or re-poll for fresh ones.        |
| `code`             | The claim code. Use it to build the QR code the holder scans to claim the credential into their wallet. |

Poll on a sensible interval and set a timeout — if the credential hasn't reached `SENT` after a reasonable window, treat it as a failure and check for errors.
{% endstep %}
{% endstepper %}
{% endtab %}
{% endtabs %}

{% hint style="warning" %}
**A signed credential can't be edited.** If the data turns out wrong after it's `ISSUED`, revoke the original and issue a fresh one — revocation is immediate and permanent, with no undo. See [Manage a Credential](/introduction-to-issuance/build/manage-a-credential.md).
{% endhint %}

***

## What happens next

Once a credential is signed and delivered, it has a life of its own — it can be accepted, held, revoked, or left to expire. Those states, and how to move a credential between them, are covered separately:

* [**Credential Lifecycle**](/introduction-to-issuance/learn/credential-lifecycle.md) — every state a credential can hold, and why it only moves forward.
* [**Manage a Credential**](/introduction-to-issuance/build/manage-a-credential.md) — put a credential on hold, revoke it, or resend delivery.
* [**Handle Errors**](/introduction-to-issuance/build/handle-errors.md) — the failure cases you'll hit, and how to recover from each.

***

## Next steps

[**Manage a Credential**](/introduction-to-issuance/build/manage-a-credential.md) — the natural sequel: what to do after a credential is out in the world.

See also: [Trust & Signing](/introduction-to-issuance/learn/trust-and-signing.md) — why the credential you just signed is trusted without a callback · [API Reference](https://dev.blerify.com/#01cf28b2-23f9-472d-a822-ab8cc0ce0114) — the full endpoint contracts.


---

# 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.blerify.com/introduction-to-issuance/build/issue-a-w3c-credential.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.
