API reference
Every endpoint, with a playground on each one. The requests go to the live network, so what you see is what your own code will get.
Base URL https://peal.network. Everything is JSON. Nothing here needs a key or an
account, and every endpoint below can be run from this page. The switch below turns on
metered mode, which sends each run through a paid twin of the
same endpoint; leave it off and the API is free, which is the default it ships as.
Ids carry forward: run Open a round and the round id fills itself into the endpoints that need one, so you can work down the page without copying anything.
Rounds
3 endpointsOpen a round
POST
/v1/rounds
Name a moment. Nothing is encrypted yet: this only says when the round opens, and gives you the id everything else hangs off.
Body
opens_in
integer
Seconds from now. Positive.
opens_at
string | integer
Instead of opens_in: RFC 3339 with an offset, or unix seconds. A time with no offset is refused.
opens_at_block
object
Instead of a clock: { chain_id, height } on a chain the network watches.
tag
string
Your app label. Up to 32 characters of a-z 0-9 : _ -. How you list your own rounds later.
title
string
Public from creation, unlike anything sealed to the round. Up to 120 characters.
description
string
Public. Up to 2000 characters.
image_url
string
Public. https only, up to 500 characters.
Headers
Idempotency-Key
string
Send one and a retry returns the same round with 200 rather than creating a second. Reusing a key with a different body is a mistake, not a retry, and answers 422 idempotency_key_reused.
Returns 201 with a Location header. Agents retry on timeouts, so send an Idempotency-Key: a duplicate round is a split auction.
Try it
Request
curl -X POST \
'https://peal.network/v1/rounds' \
-H 'content-type: application/json' \
-d '{
"opens_in": 3600,
"tag": "my-app",
"title": "Signed tour poster"
}'
Response
{
"id": "cond_…",
"status": "open",
"opens_at": "2026-09-12T18:00:00Z",
"opens_at_unix": 1789408800,
"seals": 0,
"slots_including_decoys": 0,
"tag": "my-app",
"title": "Signed tour poster"
}
List rounds
GET
/v1/rounds
Your rounds, newest first. This is the query tags exist for.
Query parameters
tag
string
Only rounds with this label.
status
string
One of open, closing, opened, stalled.
limit
integer
1 to 200. Defaults to 25.
cursor
string
From a previous response’s next_cursor.
The cursor orders on (created_at, id). Without the tie-break, rounds created in the same second get skipped or repeated as you page.
Try it
Request
curl \
'https://peal.network/v1/rounds?tag=my-app&limit=5'
Response
{
"data": [ { "id": "cond_…", "status": "open", … } ],
"next_cursor": "MTc4…",
"has_more": true
}
Read a round
GET
/v1/rounds/{id}
One URL, every stage, always 200. There is no 404 standing in for "not open yet".
Path parameters
id
string
required
The round id.
Headers
If-None-Match
string
The ETag from a previous read. An unchanged round answers 304 with no body.
Carries an ETag over the fields that actually move, so polling a deadline costs a 304 until something happens.
Try it
Request
curl \
'https://peal.network/v1/rounds/{id}'
Response
{
"id": "cond_…",
"status": "open", // open | closing | opened | stalled
"seals": 3,
"slots_including_decoys": 64,
"opens_at": "2026-09-12T18:00:00Z",
"opened_at": null
}
Seals
5 endpointsSeal a payload to a round
POST
/v1/rounds/{id}/seals
Hand over a ciphertext. The encryption happened on your machine, with batched threshold encryption (BTE) under the committee's public parameters; this endpoint has never accepted a plaintext and never will.
Path parameters
id
string
required
The round to seal to.
Body
ciphertext_b64
string
required
The sealed payload, base64. Parsed, on curve and subgroup checked before it is stored.
The id is the hash of the ciphertext, so the same submission twice is the same seal and needs no idempotency key. 409 once the round has closed.
Try it
A ciphertext cannot be typed. This encrypts a sample payload in your browser and fills the field.
Request
curl -X POST \
'https://peal.network/v1/rounds/{id}/seals'
Response
{
"id": "4f858dc3…", // sha256 of the ciphertext
"round_id": "cond_…",
"status": "sealed"
}
List a round’s seals
GET
/v1/rounds/{id}/seals
Ids and positions while the round is open; the same shape with payload_b64 once it has opened.
Path parameters
id
string
required
The round id.
Nothing can leak early here: before the reveal the coordinator does not hold a payload to leak.
Try it
Request
curl \
'https://peal.network/v1/rounds/{id}/seals'
Response
{
"data": [
{ "id": "4f858dc3…", "position": 3, "status": "opened",
"payload_b64": "…" }
],
"round": { "id": "cond_…", "status": "opened" }
}
Seal until a time
POST
/v1/seals
One payload, one deadline, one call. Creates a round holding just this seal and hands back a proof URL.
Body
ciphertext_b64
string
required
The sealed payload, base64.
unlock_in
integer
Seconds from now.
unlock_at
string | integer
Instead of unlock_in: RFC 3339 with an offset, or unix seconds.
tag
string
Your app label.
title
string
Public from creation.
Try it
A ciphertext cannot be typed. This encrypts a sample payload in your browser and fills the field.
Request
curl -X POST \
'https://peal.network/v1/seals' \
-H 'content-type: application/json' \
-d '{
"unlock_in": 3600,
"tag": "my-app"
}'
Response
{
"id": "4f858dc3…",
"round_id": "cond_…",
"unlock_at": "2026-09-12T18:00:00Z",
"proof_url": "/v1/seals/4f858dc3…/proof"
}
Read a seal
GET
/v1/seals/{id}
One seal, with its payload once the round has opened.
Path parameters
id
string
required
The seal id, which is the sha256 of its ciphertext.
Try it
Request
curl \
'https://peal.network/v1/seals/{id}'
Response
{
"id": "4f858dc3…",
"round_id": "cond_…",
"position": 3,
"status": "sealed",
"round": { "id": "cond_…", "status": "open" }
}
Read a seal’s proof
GET
/v1/seals/{id}/proof
What can actually be checked, and nothing that cannot.
Path parameters
id
string
required
The seal id.
The load-bearing field is ordering_committed_at. The batch ordering is written at freeze, before any operator is handed work, so a commitment earlier than the reveal is evidence the set was fixed before anybody could read it. Null rather than false before the round opens.
Try it
Request
curl \
'https://peal.network/v1/seals/{id}/proof'
Response
{
"seal_id": "4f858dc3…",
"position": 3,
"ordering_root": "0x…",
"ordering_committed_at": 1788490917,
"merkle_root": "0x…",
"revealed_at": 1788494517,
"commitment_precedes_reveal": true
}
Auctions
4 endpointsOpen an auction
POST
/v1/auctions
A round with the rules that decide what a bid means. Every bid is sealed with batched threshold encryption and opens only when the round fires. Rules that cannot be satisfied are refused here rather than at the close.
Body
closes_in
integer
Seconds from now.
closes_at
string | integer
Instead of closes_in: RFC 3339 with an offset, or unix seconds.
currency
string
A code from /v1/currencies. Its decimals come with it.
decimals
integer
0 to 4. Only needed for a code the table does not know.
reserve_minor
integer
Integer MINOR units. Nothing below this can win.
maximum_minor
integer
Integer minor units. Nothing above this can win. Set one: it is what stops a joke bid taking the auction.
title
string
Public from creation.
description
string
Public. Up to 2000 characters.
image_url
string
Public. https only.
contact_public_key
string
The seller’s PUBLIC key, when bidders may attach contact details. Never send the private half.
tag
string
Your app label.
bid_url is a hosted page where somebody can read the terms and bid, so an auction works before you have built an interface. check_code is eight speakable characters a seller reads out and a bidder compares.
Try it
Request
curl -X POST \
'https://peal.network/v1/auctions' \
-H 'content-type: application/json' \
-d '{
"closes_in": 3600,
"currency": "USD",
"reserve_minor": 1000,
"maximum_minor": 50000,
"title": "Signed tour poster",
"tag": "my-shop"
}'
Response
{
"id": "cond_…",
"status": "open",
"closes_at": "2026-09-12T18:00:00Z",
"currency": "USD",
"decimals": 2,
"reserve_minor": 1000,
"maximum_minor": 50000,
"bid_url": "https://peal.network/#/live/…",
"check_code": "1C8J T47V",
"terms_hash": "0x…"
}
Read an auction
GET
/v1/auctions/{id}
The rules as stored, the bid count, and the links.
Path parameters
id
string
required
The auction id.
Try it
Request
curl \
'https://peal.network/v1/auctions/{id}'
Response
{
"id": "cond_…",
"status": "open",
"bids": 4,
"closes_at": "2026-09-12T18:00:00Z",
"currency": "USD",
"reserve_minor": 1000,
"results_url": "/v1/auctions/cond_…/results"
}
Place a bid
POST
/v1/auctions/{id}/bids
A bid is a seal holding the fixed width bid record. Same validation and the same closed check as any other seal.
Path parameters
id
string
required
The auction id.
Body
ciphertext_b64
string
required
The encrypted bid record: 320 bytes before encryption, whatever the amount inside it.
Every bid is the same size on the wire. Without that the ciphertext length ranks the auction for anyone watching, before a single bid opens.
Try it
A ciphertext cannot be typed. This encrypts a sample payload in your browser and fills the field.
Request
curl -X POST \
'https://peal.network/v1/auctions/{id}/bids'
Response
{
"id": "4f858dc3…",
"round_id": "cond_…",
"status": "sealed"
}
Read the board
GET
/v1/auctions/{id}/results
Every readable bid ranked, the queue the rules allow to win, the winner, and anything discarded with the reason.
Path parameters
id
string
required
The auction id.
Before the close, bids is null rather than an empty list, so "not open yet" cannot be read as "nobody bid".
Try it
Request
curl \
'https://peal.network/v1/auctions/{id}/results'
Response
{
"status": "opened",
"bids": [ { "name": "ana", "amount_minor": 12500,
"meets_reserve": true, "within_maximum": true } ],
"queue": [ … ], // only those the rules allow to win
"winner": { "name": "ana", "amount_minor": 12500 },
"decoys": 60,
"discarded": []
}
Reference
4 endpointsService description
GET
/v1
What this deployment accepts. Read it rather than hard-coding limits from prose.
Try it
No parameters. Press run.
Request
curl \
'https://peal.network/v1'
Response
{
"service": "peal",
"version": "v1",
"limits": {
"max_payload_bytes": 5242880,
"max_page_size": 200,
"requests_per_second": 50,
"burst": 400
}
}
Public parameters
GET
/v1/parameters
The key material you encrypt against, with the digest a client checks before using it.
The client verifies the digest against the bytes it was served, so a coordinator handing out inconsistent parameters fails loudly rather than producing ciphertexts nobody can open.
Try it
No parameters. Press run.
Request
curl \
'https://peal.network/v1/parameters'
Response
{
"id": "ed707ad8…",
"digest": "ed707ad8…",
"parameters_b64": "…",
"operators": 5,
"threshold": 3,
"batch_size": 64
}
Currencies
GET
/v1/currencies
The 56 currencies the API knows, with the decimals each uses. Searchable by code, name or symbol.
Query parameters
q
string
Search by code, name or symbol. "rupee", "INR" and "₹" all find the same one.
limit
integer
1 to 200. Defaults to 200.
Pass a known code when you create an auction and the decimals come with it. The yen has none and the Kuwaiti dinar has three.
Try it
Request
curl \
'https://peal.network/v1/currencies?q=rupee&limit=5'
Response
{
"data": [
{ "code": "INR", "name": "Indian rupee", "decimals": 2, "symbol": "₹" }
],
"total": 56
}
Check a short link
GET
/v1/names/{name}
Whether a name is free, and where it points if it is not.
Path parameters
name
string
required
3 to 32 characters of a-z 0-9 and hyphens, not starting or ending with one.
Checking only. Claiming is a permanent onchain write that can never be undone or repointed, so it happens from your own key rather than from a server acting on your behalf.
Try it
Request
curl \
'https://peal.network/v1/names/shoonya'
Response
{
"name": "shoonya",
"valid": true,
"available": false,
"url": "https://peal.network/shoonya",
"permanent": true
}
Errors
Every failure is RFC 9457 problem+json with a stable code to branch on and a
field when one input is at fault. detail is for people and its
wording is not part of the contract.
{
"type": "https://peal.network/#/developers#invalid_maximum",
"title": "invalid request",
"status": 400,
"code": "invalid_maximum",
"detail": "the maximum cannot be below the reserve",
"field": "maximum_minor"
}
The full list is on limits and errors.
Rate limits
50 requests a second per IP, bursting to 400. Every response carries
RateLimit-Limit, RateLimit-Remaining and
RateLimit-Reset, so you never have to be refused to learn your budget.