Quickstart

Three calls against the live network, from this page. No signup, no API key, no wallet.

The three calls

Run them in order. Step one and three are ordinary HTTP that curl can do; step two needs the library, because that is where your data actually gets encrypted.

1. Open a round

A round is a moment and everything sealed to it. Nothing is encrypted yet. opens_in is relative; opens_at takes RFC 3339 or unix seconds; opens_at_block takes a chain height. Plain HTTP, no key, works from curl.

const res = await fetch('https://peal.network/v1/rounds', {
  method: 'POST',
  headers: {
    'content-type': 'application/json',
    // Retry-safe: the same key returns the same round, never a second one.
    'idempotency-key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    opens_in: 3600,                // or opens_at: '2026-09-12T18:00:00Z'
    tag: 'my-app',                 // how you list your own rounds later
    title: 'Signed tour poster',   // public now; the sealed payloads come next
  }),
});

const round = await res.json();
if (!res.ok) throw new Error(`${round.code}: ${round.detail}`);

2. Seal a payload to it

The only step that needs code, because this is where the encryption happens and it happens on your machine. peal.js loads straight from this domain: one file, nothing to install. Payloads are padded to a fixed width first, so the ciphertext length says nothing about what is inside it.

import { peal } from 'https://peal.network/peal.js';

// Encrypts in this process. Only the ciphertext crosses the network.
const seal = await peal.seal('my sealed bid', round.id);

// seal.id is the sha256 of that ciphertext: recompute it from your own
// copy rather than taking our word for which seal is yours.

3. Read the round

One URL, every stage, always 200. While the round is open you get its status and deadline and nothing about what is in it: seals is null until it opens, so a live round does not announce how few sealed to it. Once it opens the same call reports opened, the count becomes real and the payloads are readable. Send If-None-Match with the ETag and an unchanged poll costs a 304 instead of a body.

const res = await fetch(`https://peal.network/v1/rounds/${round.id}`, {
  headers: etag ? { 'if-none-match': etag } : {},
});
if (res.status === 304) return;           // nothing has moved
etag = res.headers.get('etag');

const state = await res.json();
if (state.status !== 'opened') return;    // 'open' | 'closing' | 'opened'

const { data } = await fetch(`https://peal.network/v1/rounds/${round.id}/seals`)
  .then((r) => r.json());

What just happened

A round is a row in the coordinator naming a moment. It fires on its own whether or not anyone is watching, which is what separates this from a commit and reveal scheme: the reveal is not a move a participant has to make, so nobody can decline it after seeing they have lost.

The payload was encrypted in your browser against the committee's public parameters, whose digest the client checked before using them. What crossed the network was already a ciphertext.

Nothing above returns a 404. Every call on this page answers 200 at every stage, and what changes is the content: seals and payload_b64 are null until the round opens. That is the guarantee working rather than an error to handle, and null is deliberate so you can tell "not yet" from "none". The older GET /v0/reveals/{id} does answer 404 before a reveal exists, which is where that status comes from if you have met it.