- Read concepts first: Issuing Credentials and Direct Issuance
- For JWT/JWKS setup and signing examples, see Partner Authentication.
Base URLs
The production base URL is for approved production partners on Moca Chain private Mainnet. See Production mainnet access before requesting mainnet $MOCA gas tokens.
Authentication
Direct AIR API requests (initialize-user and dstorage/vcs) authenticate via a Partner JWT in the x-partner-auth header, signed with partnerId and scope: "issue". The JWT does not carry an email claim — the recipient is identified in the initialize-user request body. The header must include kid matching a key in your JWKS endpoint and typ: "JWT". See Partner Authentication.
AIR calls an issuer-hosted available-vc or issue-vc endpoint with the shared issuer backend API key in the x-api-key header. Configure the same value in the issuer service (API_KEY) and AIR (issuerBackendApiKey).
Issuance surfaces
There are two ways to issue, both of which sign with issuer-controlled keys and store an encrypted envelope in DStorage:- Script-based direct issuance — your script calls
POST /auth/initialize-userto resolve the user, builds/signs/encrypts the VC, then stores it withPOST /dstorage/vcs. - Hosted Issuer Backend — you host
available-vcandissue-vc; AIR and the SDK call them to discover and trigger issuance on demand.
1) Initialize / resolve user
Resolve or create the recipient’s AIR Account and return their DID and public key.Request headers
Request body
Response
The bundled
air-issuer-service CSV runner currently uses AIR’s legacy v2 bootstrap contract: POST {AIR_API_ORIGIN}/v2/auth/initialize-user, an x-partner-id header, and { "partnerJwt": "..." } in the request body. For that compatibility contract only, the runner places the recipient email in the JWT and does not set scope. New custom integrations should use the v1 contract above: x-partner-auth, scope: "issue", and { "email": "..." } in the request body.2) Store encrypted VC
Store the issuer-signed, holder-encrypted VC envelope in DStorage.Request headers
Request body
The issuer service encrypts the signed credential with X25519 key agreement, HKDF-SHA-256, and AES-256-GCM. Its encryption helper returns
{ encryptedData, iv, authTag, dataEncPublicKey }; map encryptedData to data and dataEncPublicKey to encryptedKey when calling DStorage.
Response
201 with the storagePath / storage reference. Record it as your issuance result.
3) Issuer-hosted endpoints
For the hosted Issuer Backend path, AIR calls your endpoints with an identity it resolved server-side. Both endpoints require:holderDID alone. Use the AIR-resolved userId to look up eligibility and claims in your own systems.
POST /available-vc
Return credentials the holder can claim. schemaId and proofType are optional filters.
Response:
POST /issue-vc
Issue one credential, encrypt it to the holder, upload it to DStorage, and persist the issuance record.
On success, the endpoint returns 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.
4) Credential status and revocation
The issuer service exposes public status URLs for verifiers and admin-protected endpoints for issuer operations. SetISSUER_ORIGIN to the public HTTPS origin serving these routes; the credential status URL is embedded in every issued BJJ credential.
Public status endpoints
These endpoints do not use an API key:
Example revocation status response:
Admin endpoints
Admin endpoints require:GET /admin/issuance-history
Returns paginated issuance records. Supported query parameters:
POST /admin/revoke
Revoke the credential identified by its nonce:
revokedAt on matching issuance records.
Script example (Node.js)
A minimal script-based direct issuance flow: resolve the user, build/sign/encrypt the VC, store it in DStorage.Retry with backoff
Error reference
Troubleshooting
- If
dstorage/vcssucceeds, record thestoragePath; the credential is available for the holder to present at any verifier. - For v1 authentication errors, verify the JWT contains
partnerIdandscope: "issue"(not the recipient email), and that the header includestyp: "JWT". - Ensure your JWKS endpoint is public and
kidmaps to the signing key.