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.

Pay per call with x402 Off. Every endpoint on this page is free and needs no wallet.

Rounds

3 endpoints

Open 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 endpoints

Seal 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 endpoints

Open 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 endpoints

Service 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
}

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.