zkverify-kurier

star 1

Kurier REST API for simplified proof verification. Use when you want the simplest integration path, don't need real-time events, or are building serverless/REST-based architectures. Covers API key setup, proof submission, status polling, and aggregation.

HorizenLabs By HorizenLabs schedule Updated 4/17/2026

name: zkverify-kurier description: Kurier REST API for simplified proof verification. Use when you want the simplest integration path, don't need real-time events, or are building serverless/REST-based architectures. Covers API key setup, proof submission, status polling, and aggregation.

Kurier REST API Integration

Kurier provides HTTP-based proof verification - the simplest integration path for zkVerify.

When to Use Kurier

  • Serverless/REST architectures - No WebSocket required
  • Simple integrations - HTTP POST/GET instead of blockchain SDKs
  • Polling-based workflows - Check status periodically
  • Quick prototyping - No blockchain library dependencies

For real-time events or direct chain access, see zkverify-sdk or zkverify-rpc.

Quick Start

1. Prerequisites

  1. Sign up at kurier.xyz (mainnet) or testnet.kurier.xyz
  2. Generate an API key from the dashboard
  3. Install dependencies: npm i axios dotenv

2. Environment Setup

# .env
KURIER_API_KEY=your_generated_api_key
KURIER_API_URL=https://api.kurier.xyz  # or https://api-testnet.kurier.xyz

3. Basic Implementation

import axios from "axios";
import dotenv from "dotenv";
dotenv.config();

const API_URL = "https://api.kurier.xyz";  // or https://api-testnet.kurier.xyz

// Register VK (one-time)
const vkResponse = await axios.post(`${API_URL}/api/v1/register-vk/${process.env.API_KEY}`, {
  proofType: "groth16",
  proofOptions: { library: "snarkjs", curve: "bn128" },
  vk: vkey
});
const vkHash = vkResponse.data.vkHash;

// Submit proof
const submitResponse = await axios.post(`${API_URL}/api/v1/submit-proof/${process.env.API_KEY}`, {
  proofType: "groth16",
  vkRegistered: true,
  proofOptions: { library: "snarkjs", curve: "bn128" },
  proofData: { proof, publicSignals, vk: vkHash }
});

console.log("Job ID:", submitResponse.data.jobId);

// Poll for finalization
while (true) {
  const status = await axios.get(
    `${API_URL}/api/v1/job-status/${process.env.API_KEY}/${submitResponse.data.jobId}`
  );
  if (status.data.status === "Finalized") break;
  await new Promise(r => setTimeout(r, 5000));
}

4. With Aggregation (Cross-Chain)

Add chainId to enable aggregation for L1 verification:

const response = await axios.post(`${API_URL}/api/v1/submit-proof/${API_KEY}`, {
  proofType: "groth16",
  vkRegistered: true,
  chainId: 8453,  // Base mainnet
  proofOptions: { library: "snarkjs", curve: "bn128" },
  proofData: { proof, publicSignals, vk: vkHash }
});

// Poll until status === "Aggregated"
// Response includes aggregationDetails with merkleProof

Job Status Lifecycle

Without Aggregation (no chainId)

Queued → Valid → Submitted → IncludedInBlock → Finalized

Finalized is terminal. The proof is on zkVerify and has a stable txHash.

With Aggregation (chainId set — this is what you want for on-chain use)

[Queued → Valid] → IncludedInBlock → AggregationPending → Aggregated → (AggregationPublished)
  • Queued / Valid may not be observed at a 5s polling cadence — the proof typically reaches IncludedInBlock within the first poll.
  • IncludedInBlock = proof is in a zkVerify block. txHash and blockHash are populated from this point forward.
  • AggregationPending = waiting to be included in an aggregation batch. This is the longest-lived state — typically 1–3 minutes.
  • Aggregated = terminal for downstream use. aggregationDetails (with merkleProof, leaf, leafIndex, numberOfLeaves) is now populated. Use this for relay polling and recordValidation.
  • AggregationPublished = aggregation has been published to the destination chain. You don't need to wait for this — Aggregated is sufficient.

Observed timing (Base Sepolia, April 2026): ~5s to IncludedInBlock, ~25s to AggregationPending, ~165s to Aggregated. Total: ~3 minutes end-to-end (submit → Aggregated).

Important: With chainId set you typically do NOT see Finalized — the proof goes straight from AggregationPending to Aggregated. Do not wait for Finalized in the aggregation path. Stop polling as soon as you see Aggregated (or AggregationPublished).

Polling Best Practices

When polling with aggregation enabled (chainId set), handle each status distinctly:

const POLL_INTERVAL_MS = 5000;
const MAX_POLL_ATTEMPTS = 120; // 10 minutes

for (let i = 0; i < MAX_POLL_ATTEMPTS; i++) {
  await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));

  const res = await fetch(`${API_URL}/api/v1/job-status/${API_KEY}/${jobId}`);
  const { status, txHash, aggregationId, aggregationDetails } = await res.json();

  if (status === "Failed") {
    throw new Error("Proof verification failed");
  }

  if (status === "IncludedInBlock" || status === "AggregationPending") {
    // Proof is on zkVerify but NOT yet aggregated — keep polling.
    // txHash and blockHash are populated from IncludedInBlock onwards —
    // save them now for explorer links + on-chain recordValidation inputs.
    continue;
  }

  if (status === "Aggregated" || status === "AggregationPublished") {
    // Terminal — aggregationDetails is now populated with the Merkle proof.
    return { aggregationId, aggregationDetails, txHash, blockHash };
  }

  // Early states (Queued, Valid) — rarely observed at this polling cadence,
  // just continue.
}

throw new Error("Polling timed out");

Common mistakes:

  • Treating Finalized as terminal when chainId is set — aggregation hasn't happened yet
  • Capturing txHash at IncludedInBlock and updating DB status to "finalized" prematurely
  • Lumping multiple statuses together in a single if block — handle each distinctly

API Endpoints

Endpoint Method Description
/api/v1/register-vk/{API_KEY} POST Register verification key
/api/v1/submit-proof/{API_KEY} POST Submit proof for verification
/api/v1/job-status/{API_KEY}/{jobId} GET Check job status

See endpoints.md for complete API reference with request/response schemas.

Job Status States

Status StatusId Description
Queued 0 Waiting for processing
Valid 1 Passed optimistic verification
Submitted 2 Submitted to blockchain mempool
IncludedInBlock 3 Transaction in block
Finalized 4 Transaction finalized
Failed 5 Processing failed
Aggregated 6 Proof aggregated (requires chainId)
AggregationPublished 7 Aggregation published to destination chain

Response Fields by Status

The job-status response includes additional fields as the proof progresses:

Field Available from Description
jobId Always Job identifier
status Always Current status string
statusId Always Numeric status code
proofType Always Proof type (e.g., "groth16")
chainId Always Destination chain (null if no aggregation)
txHash IncludedInBlock onwards zkVerify extrinsic hash — use for explorer links: https://zkverify-testnet.subscan.io/extrinsic/{txHash}
blockHash IncludedInBlock onwards zkVerify block hash
aggregationId Aggregated onwards Sequential integer — use as attestationId in HL Marketplace contracts
aggregationDetails Aggregated onwards Object with leaf, leafIndex, numberOfLeaves, merkleProof, receipt, root

Important: txHash is the zkVerify blockchain extrinsic hash, not a Base/Ethereum transaction hash. Use it to build explorer links for proof transparency in your UI.

zkVerify Explorer

View finalized proofs on the zkVerify blockchain explorer:

Format: https://zkverify.subscan.io/extrinsic/{txHash}

After a proof reaches "Finalized" status, use the txHash from the job status response to view details on the zkVerify blockchain explorer. The explorer shows proof verification details, block information, transaction hashes, and gas fees.

See zkverify-explorer.md for complete details.

Additional Resources

Documentation

External Resources

Related Skills

  • hl-registry-integration — Continue the pipeline: after Aggregated, wait for attestation relay on Base Sepolia and record the validation on the HL Marketplace ValidationGateway.
  • zkverify-sdk — TypeScript SDK with real-time events (alternative to polling)
  • zkverify-attestation — Cross-chain attestation workflow
  • zkverify-contracts — Direct smart-contract verification
Install via CLI
npx skills add https://github.com/HorizenLabs/hl-claude-marketplace --skill zkverify-kurier
Repository Details
star Stars 1
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator