CA
GHOST

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.