> ## 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.

# Issuing and verifying credentials

> Build portable loyalty tier credentials with AIR Kit: schema, on-demand issuance from your loyalty engine, and tier verification at the point of benefit.

Issue loyalty tier and milestone credentials from your loyalty engine, and let partner apps verify them without an API integration with you.

<Info>
  This guide issues and verifies loyalty **credentials**. A `totalPoints` claim records an issuer's statement about accumulated points; it does not create a spendable points balance. For rewards with funding and redemption, see [Issuing loyalty points](/products/loyalty/issuing-points) and [Spending loyalty points](/products/loyalty/spending-points).
</Info>

## What you can build

* **Tier credentials** — Issue a "Gold Member" or "VIP" badge that upgrades or revokes automatically as status changes
* **Milestone badges** — Issue a credential when a user reaches 1,000 points, 10 purchases, or any event-driven threshold
* **Cross-platform loyalty** — Allow partner apps to accept your loyalty credentials as proof of status, no API integration required
* **Zero-friction issuance** — Trigger credential issuance from your loyalty engine backend; the user doesn't need to open a wallet or take any action

## Architecture

```mermaid theme={null}
graph TD
    A["User Action\ne.g. purchase, referral, check-in"] --> B[Your Loyalty Engine]
    B -->|Milestone reached| C[On-demand issuance]
    C --> D[AIR Kit / Moca Chain]
    D --> E[Loyalty Credential in User's AIR Account]

    E --> F{User presents credential}
    F -->|At your app| G[Tier unlock / reward]
    F -->|At partner app| H[Cross-brand benefit]
    F -->|At any verifier| I[Gated access / discount]
```

## Recommended schema

Create this schema in the [AIR Kit Dashboard](https://developers.sandbox.air3.com/dashboard) under **Issuer → Schema Builder**.

```json theme={null}
{
  "title": "Loyalty Tier",
  "description": "Brand loyalty membership tier credential",
  "properties": {
    "tier": {
      "type": "string",
      "enum": ["Bronze", "Silver", "Gold", "Platinum"],
      "description": "Current loyalty tier"
    },
    "totalPoints": {
      "type": "number",
      "description": "Lifetime points accumulated"
    },
    "memberSince": {
      "type": "string",
      "format": "date",
      "description": "Date of first loyalty enrollment"
    },
    "brandId": {
      "type": "string",
      "description": "Issuing brand identifier"
    }
  },
  "required": ["tier", "totalPoints", "brandId"]
}
```

## Implementation

### Step 1 — Issue a loyalty credential on milestone

Use **On-demand issuance** to issue silently when a backend event fires. The user's session is not required.

```javascript theme={null}
// loyalty-engine.js
const { getPartnerJwt } = require('./lib/jwt'); // see Partner Authentication
// issuer-controlled helpers backed by your signing keys
const { buildVc, signVc, encryptToHolder } = require('./lib/credential');

const BASE_URL = 'https://api.sandbox.mocachain.org/v1'; // swap for prod URL on launch

async function issueLoyaltyCredential({ userEmail, tier, totalPoints, memberSince }) {
  const token = await getPartnerJwt(); // JWT must include scope: "issue"

  // Resolve the member's AIR Account
  const init = await fetch(`${BASE_URL}/auth/initialize-user`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'x-partner-auth': token },
    body: JSON.stringify({ email: userEmail }),
  }).then((r) => r.json());

  // Build, sign (SD-JWT VC), and encrypt the credential to the holder
  const schemaId = process.env.LOYALTY_CRED_ID; // Dashboard → Issuer → Programs
  const vc = buildVc({
    holderDid: init.did,
    schemaId,
    credentialSubject: { tier, totalPoints, memberSince, brandId: process.env.BRAND_ID },
  });
  const encrypted = encryptToHolder(signVc(vc), init.publicKey);

  // Store the encrypted envelope in DStorage
  const res = await fetch(`${BASE_URL}/dstorage/vcs`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'x-partner-auth': token },
    body: JSON.stringify({
      holderDid: init.did,
      schemaId,
      expiresAt: vc.expirationDate,
      data: encrypted.encryptedData,
      iv: encrypted.iv,
      authTag: encrypted.authTag,
      encryptedKey: encrypted.dataEncPublicKey,
      externalId: vc.id,
    }),
  });
  if (!res.ok) throw new Error(`Issuance failed: ${res.status}`);
  const { storagePath } = await res.json();
  return storagePath;
}

// Hook into your existing loyalty event pipeline
loyaltyEngine.on('tier:upgrade', async ({ userEmail, newTier, totalPoints }) => {
  const storagePath = await issueLoyaltyCredential({
    userEmail,
    tier: newTier,
    totalPoints,
    memberSince: new Date().toISOString().split('T')[0],
  });
  console.log(`Loyalty credential issued: ${storagePath}`);
});
```

### Step 2 — Verify loyalty tier at point of benefit

On your frontend, verify the user holds the required tier before granting access or rewards.

```javascript theme={null}
// loyalty-gate.js  (frontend)
import { AirService, BUILD_ENV } from "@mocanetwork/airkit";

const airService = new AirService({ partnerId: process.env.PARTNER_ID });
await airService.init({ buildEnv: BUILD_ENV.SANDBOX }); // or BUILD_ENV.PRODUCTION

async function checkLoyaltyTier() {
  const result = await airService.verifyCredential({
    programId: process.env.LOYALTY_VERIFY_PROGRAM_ID, // Dashboard → Verifier → Programs
  });
  // Your verifier program rule: tier === "Gold" (or higher)
  return result.status === 'COMPLIANT';
}

const hasGoldStatus = await checkLoyaltyTier();
if (hasGoldStatus) {
  unlockPremiumContent();
} else {
  showUpgradePrompt();
}
```

## Key patterns

| Pattern | Reissue behavior | When to Use |
| - | :-: | - |
| Tier upgrade | Revoke + reissue | Always revoke old credential and issue new one |
| One-time milestone badge | Skip if exists | Don't re-issue if credential already held |
| Time-boxed VIP | Set `expirationDate` in subject | Seasonal or campaign-based access |
| Retroactive bulk issuance | Loop `issueLoyaltyCredential` | Migrating existing loyalty members |

## Issuance result

Issuance completes in \~1–4 seconds and returns a `storagePath` once the encrypted credential is stored in DStorage. Use that result to confirm success to the user — no status polling required. The credential is immediately available for the member to present at any verifier.

## Examples

<CardGroup cols={2}>
  <Card title="VIP Status Portability — live demo" icon="play" href="https://demo.air3.com/try-it-live/member-discount">
    Present airline Gold status at a partner café. The café never sees the membership number.
  </Card>

  <Card title="VIP Status Portability — Issuer" icon="github" href="https://github.com/MocaNetwork/air-examples/tree/main/vip-status-portability/issuer">
    Airline loyalty app: issues tier credential; user carries status to partner brands.
  </Card>

  <Card title="VIP Status Portability — Verifier" icon="github" href="https://github.com/MocaNetwork/air-examples/tree/main/vip-status-portability/verifier">
    Hotel chain app: verifies tier and grants equivalent perks (e.g. room upgrade, lounge).
  </Card>
</CardGroup>

## Next steps

<Columns cols={2}>
  <Card title="On-demand Issuance — Concepts" icon="book" href="/products/identity/issuing-credentials#direct-issuance">
    Understand when and why to use On-demand issuance.
  </Card>

  <Card title="On-demand Issuance — API" icon="code" href="/api-reference/issuance-api">
    Full endpoint reference with error codes.
  </Card>

  <Card title="Schema Use Cases" icon="list" href="/products/identity/schema-use-cases">
    More schema examples including membership and event pass.
  </Card>

  <Card title="Verifying Credentials" icon="shield-check" href="/products/identity/verify">
    SDK verification flow reference.
  </Card>
</Columns>


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