Skip to main content

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.

BLOCKING β€” Partner JWKS endpoint required. issueCredential, verifyCredential, and Issue on Behalf all fail until you (1) host a public HTTPS JWKS URL, (2) register it in Dashboard β†’ Account β†’ General β†’ JWKS URL, and (3) sign your Partner JWT with a kid that matches a key in that JWKS. Localhost is not reachable from AIR servers β€” use an HTTPS tunnel (ngrok, cloudflared) or deploy. See JWKS endpoint setup.
JWKS URL is a mandatory dashboard account field β€” peer-tier with Partner ID, Issuer DID, and Verifier DID. AIR Kit fetches your JWKS over HTTPS to validate every Partner JWT you sign, so no credential SDK call can succeed until JWKS is live, registered, and matches your signing key.

When this is required

OperationWhere it runsJWKS required?
airService.issueCredentialClient SDKYes
airService.verifyCredentialClient SDKYes
Issue on Behalf (REST)Server-to-serverYes
airService.login (account only)Client SDKOnly when you use bring your own auth
All credential operations share the same Partner JWT trust model β€” so a single JWKS setup unblocks all of them.

What AIR does with your JWKS

If AIR cannot fetch your JWKS, or the kid in your JWT header does not appear in keys[], you get a 401 and the credential flow halts.

Three-part checklist

You must complete all three. Each one alone is not enough.
1

Implement the JWKS route

Host a public HTTPS endpoint that returns a JWKS JSON document containing your Partner JWT public key. The canonical Next.js path used across air-examples is GET /api/.well-known/jwks.
2

Register the full URL in the dashboard

In the Developer Dashboard go to Account β†’ General Settings β†’ JWKS URL and paste the full HTTPS URL that your app actually serves (for example https://issuer.example.com/api/.well-known/jwks).
3

Sign Partner JWTs with a matching `kid`

The JWT header must include kid set to a key id that also appears as keys[].kid in the JSON your endpoint returns. The shipped examples use kid === partnerId.

Implement the route (Next.js)

This is the exact route shipped in every issuer and verifier app in air-examples. It reads your public key from an environment variable, converts PEM β†’ JWK using jose, and sets kid to your Partner ID.
app/api/.well-known/jwks/route.ts
import { NextResponse } from "next/server";
import * as jose from "jose";

export async function GET() {
  try {
    const publicKeyPEM = `-----BEGIN PUBLIC KEY-----\n${process.env.PARTNER_PUBLIC_KEY!}\n-----END PUBLIC KEY-----`;
    const publicKey = await jose.importSPKI(publicKeyPEM, process.env.SIGNING_ALGORITHM!);
    const jwk = await jose.exportJWK(publicKey);

    return NextResponse.json(
      {
        keys: [
          {
            ...jwk,
            kid: process.env.NEXT_PUBLIC_PARTNER_ID!,
            use: "sig",
            alg: process.env.SIGNING_ALGORITHM!,
          },
        ],
      },
      {
        headers: {
          "Content-Type": "application/json",
          "Cache-Control": "public, max-age=3600",
        },
      },
    );
  } catch {
    return NextResponse.json({ error: "Failed to generate JWKS" }, { status: 500 });
  }
}
Required environment variables (server-side only β€” never expose PARTNER_PRIVATE_KEY):
VariableUsed for
PARTNER_PUBLIC_KEYThe base64 body of your RS256 or ES256 public key (between the BEGIN/END markers).
PARTNER_PRIVATE_KEYThe matching private key used to sign your Partner JWT. Server-only.
SIGNING_ALGORITHMRS256 or ES256. Must match the key type.
NEXT_PUBLIC_PARTNER_IDYour Partner ID. Used as kid so the JWKS key matches the JWT header.
Generate the key pair with OpenSSL β€” see Partner Authentication β†’ Generating an RS256 Key Pair.

Register the URL in the Developer Dashboard

  1. Open the Developer Dashboard and connect your EOA wallet.
  2. Navigate to Account β†’ General Settings.
  3. Paste the full URL into the JWKS URL field. Examples:
    • https://issuer.example.com/api/.well-known/jwks (air-examples path)
    • https://issuer.example.com/jwks.json (plug-and-play template path)
  4. Save the settings.
Register the exact URL your app actually serves. AIR fetches this exact URL β€” there is no path discovery or fallback.

Local development (HTTPS tunnel)

AIR servers cannot reach http://localhost:3000. You must expose your local dev server over public HTTPS before issue/verify will work.
ngrok http 3000
# β†’ forwarding https://abc123.ngrok.app -> http://localhost:3000
Register https://abc123.ngrok.app/api/.well-known/jwks in the dashboard.
Before running an end-to-end issue/verify flow, gate on a curl check:
curl https://your-tunnel-host/api/.well-known/jwks
# β†’ { "keys": [ { "kty": "...", "kid": "<your-partner-id>", "alg": "RS256", ... } ] }
If you do not see a keys array with a matching kid, fix this before touching the SDK.

The kid rule

The kid (Key ID) glues the JWT to the JWKS. It must match in two places.
LocationValueSource
JWT header kidpartnerIdThe header you set when signing the Partner JWT in your backend.
JWKS keys[].kidpartnerIdReturned by your /api/.well-known/jwks route.
The shipped examples set both to your Partner ID. If you use a different convention (for example, key rotation IDs), the rule is the same β€” whatever value you put in the JWT header must appear in keys[].

Path matrix (do not blindly copy)

Two MocaNetwork-published references ship JWKS at different paths. There is no single canonical URL β€” you must register the one your deployment actually serves.
SourcePath servedWhere it comes from
air-examples (Next.js apps)/api/.well-known/jwkssrc/app/api/.well-known/jwks/route.ts
Plug-and-play issuer template/jwks.jsonGenerated at build time, served as a static asset
Rule: look at what your app actually exposes, then register that exact URL.

One Partner ID = one registered JWKS URL

Each Partner ID has a single JWKS URL slot in the dashboard. When your issuer and verifier apps share a Partner ID β€” which is the standard layout β€” you only need one JWKS endpoint registered.
  • Register the issuer’s JWKS URL (it is the side that always needs public hosting first).
  • The verifier signs its own Partner JWTs locally using the same partnerId and a kid that appears in the issuer’s published JWKS. AIR fetches the registered URL regardless of which app produced the JWT.
  • If issuer and verifier are deployed at different hosts but share a Partner ID, make sure both sign with a kid that the registered JWKS exposes.
If you genuinely need separate JWKS per service, request a second Partner ID.

Troubleshooting

When something fails after this checklist looks done, jump to the matching block in Common errors: