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.