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

# Issue on Behalf API & Examples

> Reference for the AIR Kit Issue on Behalf REST API — endpoint definitions, status polling flow, duplicate handling, error codes, and code examples.

* Read concepts first: [Issue on Behalf](/airkit/usage/credential/issue-on-behalf)
* For JWT/JWKS setup and signing examples, see [Partner Authentication](/airkit/usage/partner-authentication).

## Base URLs

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

## 1) Issue credential

Submit credential issuance for a target user.

```
POST /v1/credentials/issue-on-behalf
```

### Request headers

| Header           | Required    | Value                       |
| ---------------- | ----------- | --------------------------- |
| `Content-Type`   | Yes         | `application/json`          |
| `x-partner-auth` | Yes         | Signed Partner JWT          |
| `user-agent`     | Recommended | Identifiable service string |

### Request body

```json theme={null}
{
  "issuerDid": "did:air:id:test:5P44fsVUhPctDTWH2Nz26pZJFsg6CqyiAELTGeVQDB",
  "credentialId": "c21s70g0i54sn0023172Cv",
  "credentialSubject": {
    "age": 25,
    "location": "United States",
    "isMember": true
  },
  "onDuplicate": "revoke"
}
```

| Field               | Type   | Required | Description                                                                                           |
| ------------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------- |
| `issuerDid`         | string | Yes      | Your Issuer DID from Developer Dashboard                                                              |
| `credentialId`      | string | Yes      | Issuance Program ID                                                                                   |
| `credentialSubject` | object | Yes      | Credential data matching your schema                                                                  |
| `onDuplicate`       | string | No       | Duplicate behavior: `"ignore"` returns existing credential, `"revoke"` reissues (default: `"revoke"`) |

### Response

```json theme={null}
{
  "coreClaimHash": "239704895d0c4693d33ee7ab9e37c6eb295f178d80074a32d4ace53b1a779c44",
  "credentialId": "c21s70g0i54sn0023172Cv",
  "userUuid": "7f01c42c-02cf-4325-96ed-ba034700f724"
}
```

| Field           | Description                                        |
| --------------- | -------------------------------------------------- |
| `coreClaimHash` | Unique issuance identifier used for status polling |
| `credentialId`  | Program ID (echoed)                                |
| `userUuid`      | Target user UUID                                   |

<Warning>
  Issuance is asynchronous. Use `coreClaimHash` with the status endpoint to confirm `ONCHAIN`.
</Warning>

## 2) Check issuance status

Poll issuance status by `coreClaimHash`.

```
GET /v1/credentials/status?coreClaimHash={hash}
```

### Request headers

| Header           | Required | Value              |
| ---------------- | -------- | ------------------ |
| `x-partner-auth` | Yes      | Signed Partner JWT |

### Query parameters

| Parameter       | Required | Description                      |
| --------------- | -------- | -------------------------------- |
| `coreClaimHash` | Yes      | Value returned by issue endpoint |

### Response

```json theme={null}
{
  "coreClaimHash": "239704895d0c4693d33ee7ab9e37c6eb295f178d80074a32d4ace53b1a779c44",
  "issuanceDate": "2026-01-19 07:18:45",
  "issuerDid": "did:air:id:test:5P44fsVUhPctDTWH2Nz26pZJFsg6CqyiAELTGeVQDB",
  "vcId": "c28t30c048pe502a3713w0",
  "vcStatus": "ONCHAIN"
}
```

`vcStatus` values:

| Status         | Meaning                          |
| -------------- | -------------------------------- |
| `WAIT_ONCHAIN` | Issuance in progress             |
| `ONCHAIN`      | Credential is available on-chain |

## Examples

<Card title="Issue on Behalf — Node.js Reference" icon="github" href="https://github.com/MocaNetwork/moca-issue-on-behalf-node" cta="Test the e2e flow">
  A minimal Node.js implementation of the issue-then-poll flow: sign the Partner JWT, call `POST /credentials/issue-on-behalf`, and poll status until `ONCHAIN`. Clone it to run the end-to-end issuance flow against the sandbox.
</Card>

### Basic issue + poll (Node.js)

```js theme={null}
require("dotenv").config();
const { getPartnerJwt } = require("./lib/jwt");

const BASE_URL =
  process.env.NODE_ENV === "production"
    ? "https://mocachain-mainnet.api.air3.com/v1"
    : "https://api.sandbox.mocachain.org/v1";

async function issueOnBehalf(userEmail, credentialSubject) {
  const token = await getPartnerJwt(userEmail);

  const res = await fetch(`${BASE_URL}/credentials/issue-on-behalf`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-partner-auth": token
    },
    body: JSON.stringify({
      issuerDid: process.env.ISSUER_DID,
      credentialId: process.env.CREDENTIAL_ID,
      credentialSubject,
      onDuplicate: "revoke"
    })
  });

  if (!res.ok) throw new Error(`Issue failed: ${res.status}`);
  return res.json();
}

async function getStatus(userEmail, coreClaimHash) {
  const token = await getPartnerJwt(userEmail);
  const res = await fetch(
    `${BASE_URL}/credentials/status?coreClaimHash=${encodeURIComponent(coreClaimHash)}`,
    { headers: { "x-partner-auth": token } }
  );
  if (!res.ok) throw new Error(`Status failed: ${res.status}`);
  return res.json();
}
```

### Event-driven webhook example

```js theme={null}
app.post("/webhooks/kyc-complete", async (req, res) => {
  const { userEmail, kycLevel } = req.body;

  const issued = await issueOnBehalf(userEmail, {
    kycVerified: true,
    kycLevel,
    verifiedAt: new Date().toISOString()
  });

  res.json({ success: true, coreClaimHash: issued.coreClaimHash });
});
```

### Retry with backoff

```js theme={null}
async function issueOnBehalfWithRetry(userEmail, credentialSubject, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await issueOnBehalf(userEmail, credentialSubject);
    } catch (error) {
      if (attempt === maxRetries) throw error;
      await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 1000));
    }
  }
}
```

## Error reference

| HTTP status | Likely cause                              | Suggested fix                                           |
| ----------- | ----------------------------------------- | ------------------------------------------------------- |
| 400         | Missing/invalid request fields            | Verify `issuerDid`, `credentialId`, `credentialSubject` |
| 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                    | Recheck IDs in dashboard                                |
| 409         | Consent rejected / conflict               | Check user consent and duplicate behavior               |
| 500         | Server-side failure                       | Retry with backoff and inspect logs                     |

## Troubleshooting

* If issue call succeeds but credential is not visible, poll status until `ONCHAIN`.
* If you get authentication errors, verify JWT contains `partnerId`, `scope: "issue"`, `email` and header includes `typ: "JWT"`.
* Ensure JWKS endpoint is public and `kid` maps to the signing key.
