> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.adid.dev/llms.txt.
> For full documentation content, see https://docs.adid.dev/llms-full.txt.

# Single Issuance

##### When to use

You are issuing one credential to one subject — the most common case for ad-hoc certifications, manual KYC, etc.

##### Before you begin

* Issuer role.
* Subject DID (the holder shares it with you).
* A schema you can issue against.

##### Steps

1. Navigate to `/issuer/credentials/issue`.
   2\. Pick the **Schema** (and version) from the dropdown.
2. Pick your **Issuer DID** (auto-populated if you have only one).
3. Enter the **Subject DID**.
4. Fill the **Claims** form — the form is auto-generated from the schema's properties.
5. (Optional) Set **Validity period**:
   * `validFrom` — defaults to now.
   * `validUntil` — optional explicit expiry.
6. Toggle **Anchor credential hash on chain** if needed.
7. Click **Issue**. The portal calls `POST /api/v1/credentials/issue` (`vc.go:96`).

##### API & SDK

<table>
  <tr>
    <th>cURL</th>

    <th>TypeScript SDK</th>
  </tr>

  <tr>
    <td>
      ```bash
      curl -X POST https://adid.dev/api/v1/credentials/issue \
        -H "Authorization: Bearer $ACCESS_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{
          "issuerDid":"did:adi:0xOrg...",
          "subjectDid":"did:adi:0x9a2c...",
          "schemaId":"schema-uuid-1",
          "claims":{
            "membershipId":"mem-2026-0001",
            "tier":"gold",
            "validUntil":"2027-04-26T00:00:00Z"
          },
          "anchorOnChain": true
        }'
      ```
    </td>

    <td>
      ```ts
      const vc = await client.issueCredential({
        issuerDid:  'did:adi:0xOrg...',
        subjectDid: 'did:adi:0x9a2c...',
        schemaId:   'schema-uuid-1',
        type: ['VerifiableCredential', 'OrganisationMembership'],
        credentialSubject: {
          membershipId: 'mem-2026-0001',
          tier: 'gold',
          validUntil: '2027-04-26T00:00:00Z',
        },
        anchorOnChain: true,
      });
      console.log(vc.id, vc.proof.proofValue);
      ```
    </td>
  </tr>
</table>

Response:

```json
{
  "credential": {
    "@context":["https://www.w3.org/ns/credentials/v2"],
    "id":"urn:uuid:...",
    "type":["VerifiableCredential","OrganisationMembership"],
    "issuer":"did:adi:0xOrg...",
    "credentialSubject":{ "id":"did:adi:0x9a2c...", "membershipId":"mem-2026-0001", "tier":"gold", "validUntil":"2027-04-26T00:00:00Z" },
    "credentialStatus":{ "type":"StatusList2021Entry", "statusListIndex":"42", "statusListCredential":"..." },
    "proof":{ "type":"Ed25519Signature2020", "proofValue":"z58D..." }
  },
  "txHash":"0xabcd..."
}
```

##### Verify

The issued VC appears in `/issuer/credentials` (your issuance log) and the holder sees it in their `/credentials` list.

##### Troubleshooting

| Code                           | Cause                          | Fix                                           |
| ------------------------------ | ------------------------------ | --------------------------------------------- |
| `403 ROLE_REQUIRED`            | Role not `issuer`              | Onboard (§4.1).                               |
| `400 SCHEMA_VALIDATION_FAILED` | Claims don't match schema      | Check the schema's required fields and types. |
| `404 SUBJECT_DID_NOT_FOUND`    | Subject DID not resolvable     | Verify the DID.                               |
| `400 ISSUER_DID_DEACTIVATED`   | Your issuer DID is deactivated | Use a different issuer DID.                   |

#### 4.3.2. Bulk Issuance from CSV ##### When to use

You have hundreds-to-millions of subjects and uniform claims (one row per subject). The bulk pipeline parses the CSV, validates each row, signs in batches, and (optionally) anchors per-batch on chain.

##### Before you begin

* A CSV file with header row matching the schema's properties.
* One column for `subjectDid` (or `subjectEmail` if your platform deployment is configured to look-up DID from email — see ops config).

##### Steps

1. Navigate to `/issuer/credentials/bulk`.
   2\. Pick the **Schema**.
2. Drag-drop the CSV file (max 50 MB).
3. The portal parses the headers and shows a **Column mapping preview** — drag schema fields onto CSV columns if needed.
4. (Optional) Toggle **Anchor batch hashes**.
5. Click **Start issuance**.

The pipeline:

| Stage             | What happens                                                       | Failure handling                          |
| ----------------- | ------------------------------------------------------------------ | ----------------------------------------- |
| Parse             | CSV → rows                                                         | Reject upload on header mismatch.         |
| Validate          | Per-row JSON Schema check                                          | Failed rows recorded; pipeline continues. |
| Queue             | Issuance jobs queued for the worker                                | —                                         |
| Sign + Insert     | Worker signs VC + inserts row                                      | Failed rows recorded.                     |
| Anchor (optional) | Batch hash → SchemaRegistry / RevocationRegistry                   | Retry on RPC failure.                     |
| Report            | Final report with success/failed counts; downloadable failure CSV. | —                                         |

##### Monitoring

The portal renders a live progress bar (SSE-driven) showing `n / total processed`, `m failed`.

##### API

Bulk uploads use a multi-part endpoint:

```bash
curl -X POST https://adid.dev/api/v1/credentials/bulk-issue \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -F "schemaId=schema-uuid-1" \
  -F "issuerDid=did:adi:0xOrg..." \
  -F "anchorBatches=true" \
  -F "csv=@membership-2026.csv"
```

##### Verify

The job appears in `/issuer/credentials/bulk?jobId=...`. Once complete, download the **success CSV** (with credential IDs) and the **failure CSV** (with reasons per row).

##### Troubleshooting

| Symptom                   | Cause                                           | Fix                                       |
| ------------------------- | ----------------------------------------------- | ----------------------------------------- |
| Header mismatch on upload | CSV columns don't map to schema                 | Adjust the column mapping or fix the CSV. |
| All rows failed           | Schema is wrong, or issuer DID lacks permission | Spot-check one row with single issuance.  |
| Job stalled               | Worker queue back-pressure                      | Wait; the worker auto-resumes.            |

#### 4.3.3. Anchoring Credential Hashes ##### Why anchor?

Anchoring writes a `keccak256(serializedVC)` to the SchemaRegistry (or to a dedicated credentials index, depending on contract config). Verifiers can then prove a credential existed at a given time *without* the platform's involvement.

> ℹ️ **Info** — Anchoring credential hashes is **opt-in per credential**. For low-value credentials, skip it (saves gas). For high-stakes credentials (regulated KYC, real-estate ownership), always anchor.

##### How

* For single issuance: toggle **Anchor on chain** in the form.
* For bulk: toggle **Anchor batch hashes** (anchors one tx per batch of N rows; check ops for the current batch size).
* Programmatically: `anchorOnChain: true` on the issuance request.

##### Verify

Re-fetch the credential. The `metadata.anchored: true` flag and `metadata.txHash` should be populated.