Read concepts first: Issue on Behalf
For JWT/JWKS setup and signing examples, see Partner Authentication.
Base URLs
| Environment | Base URL |
|---|
| Sandbox | https://api.sandbox.mocachain.org/v1 |
| Production | https://api.mocachain.org/v1 |
1) Issue credential
Submit credential issuance for a target user.
POST /v1/credentials/issue-on-behalf
| Header | Required | Value |
|---|
Content-Type | Yes | application/json |
x-partner-auth | Yes | Signed Partner JWT |
user-agent | Recommended | Identifiable service string |
Request body
{
"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
{
"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 |
Issuance is asynchronous. Use coreClaimHash with the status endpoint to confirm ONCHAIN.
2) Check issuance status
Poll issuance status by coreClaimHash.
GET /v1/credentials/status?coreClaimHash={hash}
| Header | Required | Value |
|---|
x-partner-auth | Yes | Signed Partner JWT |
Query parameters
| Parameter | Required | Description |
|---|
coreClaimHash | Yes | Value returned by issue endpoint |
Response
{
"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
Basic issue + poll (Node.js)
require("dotenv").config();
const { getPartnerJwt } = require("./lib/jwt");
const BASE_URL =
process.env.NODE_ENV === "production"
? "https://api.mocachain.org/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
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
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.