> ## 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: the AIR API endpoints for initializing accounts and storing encrypted VCs, and the endpoints your issuer service hosts.

* Read concepts first: [Issuing Credentials](/products/identity/issuing-credentials)
* For JWT/JWKS setup and signing examples, see [SDK authentication](/get-started/authentication/sdk-auth).

This page covers two separate surfaces. Do not mix their base URLs or authentication headers.

| Surface | Base URL | Who calls it | Authentication |
| - | - | - | - |
| [AIR API](#air-api) | `{AIR_API_BASE_URL}` | Your backend | `x-partner-auth: <Partner JWT>` |
| [Your issuer service](#your-issuer-service): holder-facing routes | `{ISSUER_ORIGIN}` | AIR, during SDK issuance | `x-api-key: <API_KEY>` |
| [Your issuer service](#your-issuer-service): admin routes | `{ISSUER_ORIGIN}` | Your own servers only | `x-admin-api-key: <ADMIN_API_KEY>` |
| [Your issuer service](#your-issuer-service): public routes | `{ISSUER_ORIGIN}` | Verifiers and AIR | None |

# AIR API

## 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`, `/admin/*`, and the status endpoints are hosted on your issuer service under its public `ISSUER_ORIGIN`. You never call the AIR API base URL for them.
</Note>

<Note>
  Moca Chain mainnet is live. Production SD-JWT issuers register in the [production Developer Dashboard](https://developers.air3.com/dashboard); see [Production mainnet access](/get-started/environments/about#production-mainnet-access).
</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 service uses its own keys, described in [Your issuer service](#your-issuer-service). They are never sent to the AIR API.

## 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 issuance** — no holder interaction. The recipient is resolved by email with `POST /auth/initialize-user`, the VC is issuer-signed and encrypted to the returned holder public key, and the envelope is stored through `POST /dstorage/vcs`. The SD-JWT issuer service wraps these steps in `POST /admin/issue-vc` (see [Admin endpoints](#4-admin-endpoints-called-by-your-servers)). You can also call the two AIR API endpoints yourself.

## 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"
}
```

# Your issuer service

These routes run on your own issuer service at its public `ISSUER_ORIGIN`, not on the AIR API. The examples follow the SD-JWT branch of the [AIR issuer service](https://github.com/MocaNetwork/air-issuer-service/tree/main). Iden3 issuers use a separate branch; see [Iden3 credentials](/products/identity/iden3-credentials).

## 3) Holder-facing endpoints (called by AIR)

Your issuer service 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": "SD_JWT_VC"
}
```

| 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. The SD-JWT issuer service issues `SD_JWT_VC`; the Iden3 issuer service issues `BJJ_SIG_2021` |

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": "SD_JWT_VC"
    }
  ]
}
```

### `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",
  "signingKey": { "jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." } },
  "proofType": "SD_JWT_VC"
}
```

| 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 public key. The SD-JWT issuer service writes it to the credential's `cnf` claim, binding the credential to the holder |
| `userId` | string | Yes | Partner primary identifier |
| `schemaId` | string | Yes | Schema to issue |
| `proofType` | string | No | `SD_JWT_VC` on the SD-JWT issuer service, `BJJ_SIG_2021` on the Iden3 issuer service. Each service rejects the other value |

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) Admin endpoints (called by your servers)

Admin routes require `x-admin-api-key: <ADMIN_API_KEY>`. Call them only from your own servers; never from a browser or through AIR.

| Method | Path | Purpose |
| - | - | - |
| `POST` | `/admin/issue-vc` | Issue directly to a user by email, without a user session |
| `POST` | `/admin/revoke` | Revoke a credential by its `revocationNonce`. See [Revoke credentials](/products/identity/revocation) |
| `GET` | `/admin/issuance-history` | Paginated issuance history (`page`, `limit`, `order`, `holderDid`, `schemaId`, `revocationNonce`) |
| `POST` | `/admin/publish-token-status-list` | Rebuild and publish status list partitions, when the [token status list](/products/identity/revocation#token-status-list) is enabled |

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

Resolves the holder through AIR `initialize-user`, signs the SD-JWT VC, encrypts it to the holder, and uploads it to DStorage. No registered schema class is needed.

```json theme={null}
{
  "userId": "member@example.com",
  "schemaId": "c21s70g0i54sn0023172Cv",
  "vct": "c21s70g0i54sn0023172Cv",
  "expiration": "2030-01-01T00:00:00Z",
  "credentialSubject": { "tier": "Gold" },
  "disclosureFrame": { "_sd": ["tier"] }
}
```

| Field | Type | Required | Description |
| - | - | - | - |
| `userId` | string | Yes | The email AIR knows the user by |
| `schemaId` | string | Yes | Schema the credential is built on |
| `vct` | string | Yes | Credential type |
| `expiration` | ISO 8601 string | Yes | Must be in the future |
| `credentialSubject` | object | Yes | Claims. Must not contain `cnf`, `exp`, `iat`, `id`, `iss`, `nonce`, `status`, `sub`, `vct`, or `vct#integrity` |
| `disclosureFrame` | object | No | Selectively disclosable claims. Defaults to every top-level claim |

Credentials issued this way have no `cnf` holder key.

## 5) Public endpoints (called 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. For SD-JWT issuers it also defines the issuer DID (`did:web:<host>`), so changing it changes your issuer identity.

| Endpoint | Purpose | Response |
| - | - | - |
| `GET /.well-known/did.json` | Issuer `did:web` document with the keys that verify your credentials | DID document |
| `GET /.well-known/jwt-vc-issuer` | SD-JWT VC issuer metadata | `{ issuer, jwks }` |
| `GET /.well-known/air-partner-info` | Partner info referenced from the DID document | `{ partnerId }` |
| `GET /revocation-status/:nonce` | Check whether a credential has been revoked | `{ "isRevoked": boolean }` |
| `GET /statuslist/:partition` | Signed token status list partition, when enabled | `application/statuslist+jwt` |

The Iden3 issuer service also serves `GET /credential-status/:nonce`, which returns the non-revocation Merkle proof and issuer tree state. See [Iden3 credentials](/products/identity/iden3-credentials).

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 to any AIR 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 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.