HTTP APIs
zk-pad runs three kinds of off-chain service. Anyone can run their own instance of each.
| Service | Purpose | Contract |
|---|---|---|
| Relayer | Submits signed FeeVault actions and pays the gas | packages/sdk/API.md + packages/sdk/src/api.ts (zod, normative) |
| Attestor | Social-account commitments, OAuth, BindOwner signatures | same |
| Indexer | Read API for the dApp: tokens, trades, candles, holders, activity | services/indexer/API.md + services/indexer/src/api/types.ts |
Source of truth
These pages mirror packages/sdk/API.md, services/indexer/API.md and the code. If a page and
the zod schemas or types.ts disagree, the code wins.
Conventions
- JSON in and out (
content-type: application/json). - Integers that can exceed 2^53 (amounts, nonces, deadlines, delays, gas) are decimal
strings. Addresses and byte strings are
0xhex. Timestamps are unix seconds. - Errors are non-2xx responses with
{ "error": string, "code"?: string }. Codes:bad_request,bad_signature,fee_too_low,expired,simulation_failed,rate_limited,screened,not_found,unauthorized,unsupported,unavailable(503, retry),internal, andpayment_required/payment_failed(402, paid attestor handle lookups). - CORS:
*for GET; allow-listed origins for POST (relayer and attestor). - Request bodies are size-limited (413
bad_request): 64 KiB on the relayer, 16 KiB on the attestor. /v1/metricson the relayer and the attestor is operator-only (Authorization: Bearer <METRICS_TOKEN>, 404 when unset), and the indexer's GraphQL is operator-only as well. Even aggregate counters polled every few seconds line up with on-chain events.
Privacy rules
- Services never log IPs, beneficiary ids, recipients or signatures. Only aggregate counters are kept. No analytics.
- Clients send no cookies (
credentials: 'omit') and no referrer. - Every endpoint must work over Tor / OHTTP. Nothing depends on the client IP except coarse in-memory rate limiting.
- Beneficiary data is served as a fetch-everything dump (
GET /v1/accountson the relayer,GET /v1/beneficiarieson the indexer). There is deliberately no per-id endpoint, so a server cannot learn which id a visitor cares about. - Nothing account-specific in URLs, which end up in proxy and CDN access logs: relayer quotes
carry no amount, tx status is a
POST, OAuth starts are aPOST, and ids only travel inPOSTbodies. - No id-specific RPC reads. Clients take nonces and balances from
GET /v1/accounts(RelayerClient.accountNonces/findAccount) instead ofnonces(id)/accountOf(id)/balanceOf(id, …)against a third-party RPC, and the attestor answers logins from its own event-sourced index. A per-ideth_callwould link the caller's IP to the id at the moment it acts.