HTTP API
All endpoints are served by the Next.js app (app/src/app/api). JSON in, JSON out, Cache-Control: no-store unless noted. Errors: { "error": string } with an HTTP status.
Public
GET /api/config
Deployment parameters. No user data.
{
"cluster": "devnet",
"registryDomain": "localhost:3000",
"trustedAttesters": ["9keo…"],
"attester": "9keo…",
"snapshotMints": ["…"],
"allowPublicSnapshot": true,
"demoAppId": "http://localhost:3000/demo",
"latestSnapshot": { /* SnapshotAttestation */ }
}
GET /api/snapshots
The 20 most recent attestations (no entries).
GET /api/snapshots/latest
Full PublishedSnapshot ({ attestation, entries[] }). Clients download it whole so the server never learns which identity is about to prove. Cache-Control: public, max-age=15. 404 if none.
GET /api/snapshots/:id
Full snapshot by id (^[0-9]{10}-[0-9a-f]{8}$). Immutable, cached for a year.
POST /api/registry
Register or revoke an identity. Rate limit: 20/min/IP.
{
"payload": {
"action": "register",
"wallet": "<base58>",
"identityCommitment": "<decimal field element>",
"walletKeyCommitment": "<decimal field element>", // "" for revoke
"registry": "localhost:3000",
"issuedAt": "2026-10-14T06:00:00.000Z",
"nonce": "<32-64 hex>"
},
"signature": "<base58 Ed25519 signature over registrationMessage(payload)>"
}
Responses: 200 {ok, registeredAt | revokedAt}, 400 malformed/expired/wrong registry, 401 bad signature, 404 revoke of unknown identity, 409 nonce reused / different wallet key / duplicate commitment, 429 limit.
The signed text is produced by registrationMessage() in the SDK; the server rebuilds it from the payload and never accepts a client-supplied message string.
POST /api/verify
Stateless verification of { bundle } against this deployment's trusted attesters. 200 valid / 422 invalid, body is a VerificationResult. No replay protection.
Demo relying party (Ghost Gate)
POST /api/demo/request
{ mint, minBalance (raw u64 string), nullifierMode: "wallet"|"identity", label? } → { request }. appId = ${GHOST_PUBLIC_URL}/demo, TTL 15 min, max snapshot age 7 days.
POST /api/demo/verify
{ bundle } → VerificationResult with replay protection. Wallet-mode requests enforce unique nullifiers; identity-mode requests allow repeat access.
Admin
POST /api/snapshots
Takes a snapshot. Requires Authorization: Bearer $GHOST_ADMIN_TOKEN, unless GHOST_ALLOW_PUBLIC_SNAPSHOT=true (development only; refused when NODE_ENV=production; 3/min/IP). Returns { ok, attestation }. Concurrent calls share one in-flight snapshot.