> ## Documentation Index
> Fetch the complete documentation index at: https://docs.air3.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Issuance API Reference

> AIR credential issuance API reference: initialize accounts, store encrypted VCs in DStorage, and host the issuer /available-vc and /issue-vc endpoints.

* Read concepts first: [Issuing Credentials](/airkit/usage/credential/issuing-credentials)
* For JWT/JWKS setup and signing examples, see [Partner Authentication](/airkit/usage/partner-authentication).

## AIR API base URL

This base URL applies only to the AIR-hosted endpoints on this page — `POST /auth/initialize-user` and `POST /dstorage/vcs` — shown below as `{AIR_API_BASE_URL}`.

| Environment | Base URL                                    |
| ----------- | ------------------------------------------- |
| Sandbox     | `https://api.sandbox.mocachain.org/v1`      |
| Production  | `https://mocachain-mainnet.api.air3.com/v1` |

<Note>
  `/available-vc`, `/issue-vc`, and the credential-status endpoints are hosted on your issuer backend under its public `ISSUER_ORIGIN`. AIR calls these endpoints during hosted SDK issuance — you never call the AIR API base URL for them.
</Note>

<Note>
  The production base URL is for approved production partners on Moca Chain private Mainnet. See [Production mainnet access](/airkit/environments#production-mainnet-access) before requesting mainnet \$MOCA gas tokens.
</Note>

## Authentication

Direct AIR API requests for direct issuance use a Partner JWT sent in the `x-partner-auth` header. The JWT must include the partner identity, at minimum `partnerId`, and for issuance flows should be scoped for issuance, e.g. `scope: "issue"` where required by the endpoint. The JWT should be signed with the partner’s private key and verifiable through the configured JWKS endpoint; its header should include `kid` matching the JWKS key and `typ: "JWT"`. For the newer `initialize-user` flow, the recipient/user identifier, typically `email`, is passed in the `initialize-user` request body, and the returned DID/public key are then used when storing the encrypted VC through `/dstorage/vcs`.

Your issuer backend protects `POST /available-vc` and `POST /issue-vc` with a shared API key in the `x-api-key` header. Configure this value as `API_KEY` in the issuer service and register the same value with AIR during issuer activation. During hosted SDK issuance, AIR—not the browser—calls these endpoints and sends the key. Never expose this key to frontend code.

## Issuance surfaces

There are two issuance patterns. In both cases, the issuer controls the signing keys and the resulting encrypted credential is stored in DStorage.

* **Hosted SDK issuance** — holder present. Your frontend calls `air.issueCredential(...)`. AIR resolves the authenticated holder and calls your registered issuer backend's `POST /available-vc` to retrieve an encrypted credential-subject preview. After the holder confirms, AIR calls `POST /issue-vc`. The issuer backend generates the authoritative claims, signs the VC, encrypts it to the holder's public key, persists the issuance record, and uploads the envelope to DStorage.
* **Direct (on-demand) issuance** — no holder interaction. Your backend calls `POST /auth/initialize-user` with the recipient's email, builds and issuer-signs the VC, encrypts it to the returned holder public key, and stores the encrypted envelope through `POST /dstorage/vcs`. The issuer service's `batch-issue-vc-csv` runner is a reference implementation of this path; it does not go through `/issue-vc`.

## 1) Initialize or resolve an AIR account

Resolve or create the recipient’s AIR Account using their email address, and return the AIR user UUID, holder DID, and public key required for direct issuance.

```text theme={null}
POST {AIR_API_BASE_URL}/auth/initialize-user
```

### Request headers

| Header           | Required | Value              |
| ---------------- | -------- | ------------------ |
| `Content-Type`   | Yes      | `application/json` |
| `x-partner-auth` | Yes      | Signed Partner JWT |

### Request body

```json theme={null}
{
  "email": "user@example.com"
}
```

| Field   | Type   | Required | Description                                                                                                                                                                    |
| ------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `email` | string | Yes      | The individual recipient's AIR Account email. AIR uses this value to resolve or create the destination account. Do not use a partner, service, admin, or shared email address. |

### Response

```json theme={null}
{
  "userId": "7f01c42c-02cf-4325-96ed-ba034700f724",
  "did": "did:air:id:test:5P44fsVUhPctDTWH2Nz26pZJFsg6CqyiAELTGeVQDB",
  "publicKey": "0x04a1..."
}
```

| Field       | Description                                                                                                                                                                   |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `userId`    | AIR user UUID for the resolved or newly created AIR account. This is not necessarily the same identifier as the partner primary `userId` supplied to issuer-hosted endpoints. |
| `did`       | Holder DID used as `holderDid` when storing the credential                                                                                                                    |
| `publicKey` | Holder public key; encrypt the VC payload to this key                                                                                                                         |

## 2) Store encrypted VC

Store an encrypted VC envelope in DStorage. Before calling this endpoint, the issuer must build and sign the VC, then encrypt the complete signed credential to the holder’s public key returned by `initialize-user`.
AIR and DStorage receive the encrypted envelope and do not construct or sign the credential.

```text theme={null}
POST {AIR_API_BASE_URL}/dstorage/vcs
```

### Request headers

| Header           | Required | Value              |
| ---------------- | -------- | ------------------ |
| `Content-Type`   | Yes      | `application/json` |
| `x-partner-auth` | Yes      | Signed Partner JWT |

### Request body

```json theme={null}
{
  "holderDid": "did:air:id:test:5P44fsVUhPctDTWH2Nz26pZJFsg6CqyiAELTGeVQDB",
  "schemaId": "c21s70g0i54sn0023172Cv",
  "expiresAt": "2027-07-28T08:00:00.000Z",
  "data": "<base64 ciphertext>",
  "iv": "<base64 initialization vector>",
  "authTag": "<base64 authentication tag>",
  "encryptedKey": "<base64 ephemeral public key>",
  "externalId": "urn:uuid:7f01c42c-02cf-4325-96ed-ba034700f724"
}
```

| Field          | Type            | Required | Description                                                                                                                                                                          |
| -------------- | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `holderDid`    | string          | Yes      | Holder DID from `initialize-user`                                                                                                                                                    |
| `schemaId`     | string          | Yes      | Schema the credential is built on                                                                                                                                                    |
| `expiresAt`    | ISO 8601 string | Yes      | Credential expiration time                                                                                                                                                           |
| `data`         | base64 string   | Yes      | Encrypted, issuer-signed VC                                                                                                                                                          |
| `iv`           | base64 string   | Yes      | AES-GCM initialization vector                                                                                                                                                        |
| `authTag`      | base64 string   | Yes      | AES-GCM authentication tag                                                                                                                                                           |
| `encryptedKey` | base64 string   | Yes      | Ephemeral data-encryption public key                                                                                                                                                 |
| `externalId`   | string          | Yes      | Stable issuer-controlled unique identifier for this credential. Reuse the same value when retrying the same storage operation; do not generate a new value for each transient retry. |

### Response 201

```json theme={null}
{
  "storagePath": "dstorage://vc/7f01c42c/c28t30c048pe502a3713w0",
  "state": "...",
  "envelopeVersion": "...",
  "createdAt": "2026-08-26T12:37:19.000Z"
}
```

## 3) Issuer-hosted endpoints

Your issuer backend exposes `POST /available-vc` and `POST /issue-vc` for hosted SDK issuance. AIR—not the browser—calls these registered endpoints with the holder identity resolved through the authenticated AIR session.

Both endpoints require:

```http theme={null}
Content-Type: application/json
x-api-key: <issuerBackendApiKey>
```

Configure the same key as `API_KEY` in the issuer service and register it with AIR during issuer activation. These routes are hosted under your issuer backend’s public `ISSUER_ORIGIN`, not under the AIR API base URL.

Do not authorize issuance or retrieve claims from `holderDID` alone. Use the AIR-resolved partner primary `userId` to look up the holder’s eligibility and authoritative claims in your own systems. Do not assume this value is interchangeable with the AIR user UUID returned by `initialize-user`.

### `POST {ISSUER_ORIGIN}/available-vc`

Return credentials the holder can claim. `schemaId` and `proofType` are optional filters. AIR calls this endpoint during hosted SDK issuance to discover credentials available to the authenticated holder. The issuer backend validates eligibility using `userId`, generates authoritative claims, encrypts the preview to `pubKey`, and returns it to AIR.

<Note>
  `schemaId` is a credential schema ID—not the Dashboard issuance program ID
</Note>

```json theme={null}
{
  "holderDID": "did:air:id:test:5P44fsVUhPctDTWH2Nz26pZJFsg6CqyiAELTGeVQDB",
  "pubKey": "0x04a1...",
  "userId": "partner-user-123",
  "schemaId": "c21s70g0i54sn0023172Cv",
  "proofType": "BJJ_SIG_2021"
}
```

| Field       | Type   | Required | Description                                                                                                                                                                          |
| ----------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `holderDID` | string | Yes      | Holder DID                                                                                                                                                                           |
| `pubKey`    | string | Yes      | Holder public key used to encrypt each credential subject preview                                                                                                                    |
| `userId`    | string | Yes      | Partner primary identifier                                                                                                                                                           |
| `schemaId`  | string | No       | Return only this schema                                                                                                                                                              |
| `proofType` | string | No       | Return only this proof type. One of `BJJ_SIG_2021`, `IDEN3_MTP`, `SD_JWT_VC`. Only `BJJ_SIG_2021` and `SD_JWT_VC` schema groups are enumerated, so `IDEN3_MTP` returns an empty list |

Response:

```json theme={null}
{
  "data": [
    {
      "holderDID": "did:air:id:test:5P44fsVUhPctDTWH2Nz26pZJFsg6CqyiAELTGeVQDB",
      "schemaId": "c21s70g0i54sn0023172Cv",
      "credentialSubject": {
        "encryptedData": "<base64 ciphertext>",
        "iv": "<base64 initialization vector>",
        "authTag": "<base64 authentication tag>",
        "dataEncPublicKey": "<base64 ephemeral public key>"
      },
      "proofType": "BJJ_SIG_2021"
    }
  ]
}
```

### `POST {ISSUER_ORIGIN}/issue-vc`

AIR calls this endpoint after the holder confirms hosted issuance. The issuer backend regenerates or retrieves the authoritative claims, signs the complete VC, encrypts it to `pubKey`, persists the issuance record, uploads the encrypted envelope to DStorage, and returns HTTP `201` with an empty body.

```json theme={null}
{
  "holderDID": "did:air:id:test:5P44fsVUhPctDTWH2Nz26pZJFsg6CqyiAELTGeVQDB",
  "pubKey": "0x04a1...",
  "userId": "partner-user-123",
  "schemaId": "c21s70g0i54sn0023172Cv",
  "proofType": "BJJ_SIG_2021"
}
```

| Field           | Type               | Required                                | Description                                                                                                                                          |
| --------------- | ------------------ | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `holderDID`     | string             | Yes                                     | Holder DID                                                                                                                                           |
| `pubKey`        | hexadecimal string | Yes, unless `encryptionKey` is supplied | Holder public key used to encrypt the complete issued credential                                                                                     |
| `encryptionKey` | hexadecimal string | No                                      | Alternative to `pubKey`. If both are present, `encryptionKey` is used for encryption                                                                 |
| `signingKey`    | object             | No                                      | `{ "jwk": { ... } }` holder key binding used as `cnf` for `SD_JWT_VC` issuance. Ignored for BJJ credentials                                          |
| `userId`        | string             | Yes                                     | Partner primary identifier                                                                                                                           |
| `schemaId`      | string             | Yes                                     | Schema to issue                                                                                                                                      |
| `proofType`     | string             | No                                      | One of `BJJ_SIG_2021`, `IDEN3_MTP`, `SD_JWT_VC`; defaults to `BJJ_SIG_2021`. Any value other than `SD_JWT_VC` is issued through the BJJ signing path |

On success, the endpoint returns `201` with an empty response body. The issuer backend uploads the encrypted credential and stores the DStorage response internally; callers should not expect a `storagePath` in this response. A missing or mismatched `x-api-key` on either issuer-hosted endpoint returns `403`, not `401`.

## 4) Credential status and revocation

The issuer service exposes public credential-status URLs used by verifiers. These routes do not require an API key. `ISSUER_ORIGIN`  must be the stable public HTTPS origin of the issuer service, without a trailing slash. The issuer service embeds a URL under this origin into each issued BJJ credential. Changing the origin later can break status resolution for previously issued credentials.

### Public status endpoints

These endpoints are hosted under `ISSUER_ORIGIN` and do not use an API key:

| Endpoint                        | Purpose                                                                               | Response                   |
| ------------------------------- | ------------------------------------------------------------------------------------- | -------------------------- |
| `GET /credential-status/:nonce` | Return the non-revocation Merkle proof and issuer tree state for the credential nonce | `{ mtp, issuer }`          |
| `GET /revocation-status/:nonce` | Check whether a credential nonce has been revoked                                     | `{ "isRevoked": boolean }` |

Example revocation status response:

```json theme={null}
{
  "isRevoked": false
}
```

## Error reference

| HTTP status | Likely cause                              | Suggested fix                                                             |
| ----------- | ----------------------------------------- | ------------------------------------------------------------------------- |
| 400         | Missing/invalid request fields            | Verify `email`, `holderDid`, `schemaId`, and all encrypted payload fields |
| 401         | Invalid/expired JWT, JWKS mismatch        | Validate signature, `kid`, `typ: "JWT"`, token expiry                     |
| 403         | Feature not enabled or schema not allowed | Enable feature in dashboard, verify schema ownership                      |
| 404         | Unknown issuer/program/user               | Recheck IDs and recipient email in dashboard                              |
| 409         | Consent rejected / conflict               | Check user consent and duplicate handling                                 |
| 500         | Server-side failure                       | Retry with backoff and inspect logs                                       |

## Troubleshooting

* If `dstorage/vcs` succeeds, record the `storagePath`; the credential is available for the holder to present at any verifier.
* If AIR never calls `/available-vc` or `/issue-vc`, confirm that the Issuer DID, Partner ID, API key, and both public endpoint URLs have been registered and activated by AIR.
* For v1 authentication errors, verify the JWT contains `partnerId` and `scope: "issue"` (not the recipient email), and that the header includes `typ: "JWT"`.
* Ensure your JWKS endpoint is public and `kid` maps to the signing key.
* The SDK’s `credentialId` is the Dashboard issuance program ID. The issuer backend receives `schemaId`. Passing one in place of the other can result in “schema not found” or routing failures.
