# zkVerifier > Payment-bridged proof submission for zkVerify. Agents POST a proof, settle USDC via x402, and get a zkVerify submission receipt. API surface may change at any time. ## API Reference - [OpenAPI schema](https://zkverifier.net/openapi.json): machine-readable OpenAPI 3.x spec - [Swagger UI](https://zkverifier.net/explorer): interactive API reference - [ReDoc](https://zkverifier.net/redoc): alternative human-readable docs ## Networks The active network is set by the `NETWORK` environment variable at deploy time. `testnet` is the default; `mainnet` requires an opt-in. - **Testnet:** Base Sepolia (`eip155:84532`). USDC (`0x036CbD53842c5426634e7929541eC2318f3dCF7e`). Facilitator: `https://x402.org/facilitator`. Public base URL: `https://testnet.zkverifier.net/v1/proofs`. - **Mainnet:** Base (`eip155:8453`). USDC (`0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`). Facilitator: Coinbase CDP (`https://api.cdp.coinbase.com/platform/v2/x402`). The `X402_NETWORK` env var carries the EIP-155 chain identifier. It defaults to `eip155:84532` for testnet and flips to `eip155:8453` on mainnet. See `api/src/zkpay_api/config.py` lines 97 and 115. ## Rate Limits Per-IP sliding-window limits apply to all `/v1/*` endpoints (health checks at `/health` and `/ready` are exempt). - Testnet: 10 requests per minute per IP - Mainnet: 60 requests per minute per IP Limits are enforced by the middleware in `api/src/zkpay_api/middleware/rate_limit.py`. When exceeded, the server returns HTTP 429 with a `Retry-After` header. ## How agents use zkVerifier Submit a proof and pay for the submission: 1. **POST /v1/proofs** with body containing `proofType` (always `"ultrahonk"`), `version` (`"V0_84"` or `"V3_0"`), and `proofData` with fields `vk` (hex), `proof` (list of hex strings, each 64 hex chars = 32 bytes, representing BN254 Fr elements), and `publicSignals` (list of hex strings, each 64 hex chars = 32 bytes). Optionally include `idempotencyKey` for dedup. Returns HTTP 201 with `jobId`, `status`, `proofHash`, `quote` (with `quoteCurrency`, `vfyPerQuote`, `quoteAtomic`, `expiresAt`), `fulfillUrl`, and `eventsUrl`. The `idempotencyKey` field deduplicates retried submissions. Same key + same proof hash returns the existing job (200 OK). Same key + different hash returns 409 Conflict. See `api/src/zkpay_api/routes/proofs.py` lines 789-817. 2. **POST {fulfillUrl}** without a payment header returns HTTP 402 with `PAYMENT-REQUIRED` header and body containing `x402Version`, error info, and `accepts` array with fields: `scheme` (`"exact"`), `network` (EIP-155 chain ID), `asset` (USDC contract address), `amount` (atomic USDC as string), `payTo` (our receive address), `maxTimeoutSeconds`, and `extra` with EIP-712 domain params (`name`, `version`, `token_address`, `decimals`, `symbol`). 3. **Sign an EIP-3009 `transferWithAuthorization`** against the USDC contract using the domain from `extra` in the 402 response. The domain uses the on-chain ERC-20 token name (`"USDC"`) and version (`"2"`). Encode the signed payload as base64 JSON. Send it as the `PAYMENT-SIGNATURE` header on the next POST. 4. **POST {fulfillUrl}** with the `PAYMENT-SIGNATURE` header returns HTTP 200 with `status`, `job_id`, and `payment` info (`is_valid`, `payer`, `transaction`, `network`). 5. **Poll** `GET {eventsUrl}` for completion events. The event sequence is: `proof_received` -> `quoted` -> `payment_required` -> `funds_received` -> `proof_submitted` -> `included` -> `succeeded`. (No `failed` event is emitted; clients should treat HTTP 5xx responses as terminal failure.) The x402 SDK (`x402[fastapi,httpx,evm]==2.21.0`) handles the 402 handshake on both sides. See `api/src/zkpay_api/x402.py`. ## Proof Schema We accept UltraHonk proofs. The zkVerify pallet currently supports two version families: | Version | VK size | Description | |---|---|---| | `V0_84` (Legacy) | 1760 bytes | bb 0.84.0+ with `--oracle_hash keccak`. Default on testnet. | | `V3_0` | 1888 bytes | bb 0.87.0+ with `--oracle_hash keccak`. | Each element in `proofData.proof` must be exactly 32 bytes (64 hex chars, one BN254 Fr element). Each element in `proofData.publicSignals` must also be exactly 32 bytes. The `vk` field is a hex string whose byte length must match the expected size for the chosen version. Size mismatch returns 422 PROOF_INVALID before any payment is requested. The body size limit is 2 MiB (2,097,152 bytes). Oversized bodies return 413. See `api/src/zkpay_api/routes/proofs.py` line 191. Supported proof types: `ultrahonk` only. Other types return 422 PROOF_TYPE_UNSUPPORTED. ## Pricing Quotes are computed from the Substrate transaction-payment formula using per-pallet weight data from `PALLET_FEE_TABLE` in `api/src/zkpay_api/config.py` (lines 312-397). The formula is: ``` verify_fee = PICOFEE_ATOMIC_VFY * ref_time + BYTEFEE_ATOMIC_VFY * body_size total_vfy_fee = verify_fee + register_fee (if registering a new VK) ``` The VFY fee is then converted to USDC atomic using a VFY/USDC oracle tick from the `oracle_ticks` table. The conversion multiplies by `fee_multiplier` (the Substrate NextFeeMultiplier, a dynamic congestion factor read from `oracle_ticks`), divides by `vfy_per_usdc` (VFY atomic per 1 USDC atomic, stored at x10^18 precision), and applies a 15% price buffer: ``` usdc_atomic = total_vfy_fee * fee_multiplier / vfy_per_usdc * 1.15 * 10^6 ``` If no oracle tick exists, the server returns 503 (fee_multiplier is a hard requirement per PRD ยง3). The minimum quote is $0.01 USDC (10,000 atomic units, since USDC has 6 decimals) and the maximum is $5.00 (5,000,000 atomic units). See `api/src/zkpay_api/routes/proofs.py` lines 358-475 and `api/src/zkpay_api/config.py` lines 122-125. ## Source - Spec: x402 v2 (https://github.com/x402-foundation/x402/blob/main/specs/transports-v2/http.md) ## Contact Early-stage hosted service; report issues via the channel you received this URL from.