Prediction Pools API
Closest-guess price contest. Each pool runs on a fixed cadence — btc-5m settles every 5 minutes, btc-4h every 4 hours, and event-5m (the tradable-event pool on the landing page; the slug is historical — it settles every 15 minutes). BTC pools predict BTC/USD; event-5m predicts the live event index — the price-weighted basket of the assets moved by the currently-featured news headline (the /state response's subject object carries the headline card and its pw_index). The prediction closest to the settlement price (P1) takes the whole pool. Stakes run $0.10 to $1,000,000 per round. One prediction per user per round: re-entering tops up your stake at the price you already predicted, and the price itself can never be changed — a top-up carrying a different predicted_price is rejected with 400 "you already predicted $X this round — top-ups must use the same price". (Allowing a change would let a player chase the live index with their lock-in already placed.) Cancel and re-enter if you want a different price while entries are open. Every endpoint below works identically for all three pools — just pass pool=event-5m. pool defaults to btc-4h when omitted and is echoed back in every response, so always read it off the response rather than assuming.
Round Lifecycle
Every round moves through: open (predictions accepted, and cancellable for a full refund — this is the status the entry-accepting round carries) → locked (entries frozen at P0, measuring window running) → settled (P1 taken; the closest prediction wins the pool) or voided (index unavailable past the grace window — fail-closed, all stakes refunded in full). The /state endpoint returns all three views at once: the open entry_round (status: "open"), the measuring_round (status: "locked"), and the last_round. Filter the live active round on status == "open", not "entry".
Authentication
Read endpoints are public. POST /api/v1/btc-pool/enter, POST /api/v1/btc-pool/cancel and GET /api/v1/btc-pool/me require a JWT — all three return 401 without one.
curl -X POST https://cymetica.com/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "…"}'
# → {"access_token": "…", "token_type": "bearer"}
# Then on every authenticated call:
# Authorization: Bearer <access_token>
Endpoints
/api/v1/btc-pool/state?pool=btc-5mPublic state of one pool: the open entry round (with the anonymized prediction tape), the locked measuring round, the last terminal round, and the live BTC/USD index. pool defaults to btc-4h.
curl "https://cymetica.com/api/v1/btc-pool/state?pool=btc-5m"
{
"pool": "btc-5m",
"enabled": true,
"now": "2026-07-21T04:20:00+00:00",
"entry_round": {
"pool": "btc-5m",
"round_epoch": 5948760,
"status": "open",
"entry_opens_at": "2026-07-21T04:20:00+00:00",
"locks_at": "2026-07-21T04:25:00+00:00",
"entries_close_at": "2026-07-21T04:24:40+00:00",
"entry_cutoff_seconds": 20,
"cadence_seconds": 300,
"settles_at": "2026-07-21T04:30:00+00:00",
"p0": null, "p1": null,
"winning_price": null, "winning_distance": null,
"winners_count": null, "void_reason": null,
"entries": 2,
"pool_usdc": "0.20",
"min_entry_usdc": "0.10",
"max_entry_usdc": "1000000",
"fee_pct": "0",
"subject_ref": null,
"predictions": [
{"id": 4812, "price": "65476.59", "amount_usdc": "0.10", "side": "up"},
{"id": 4813, "price": "65493.59", "amount_usdc": "0.10", "side": "up"}
]
},
"measuring_round": { "...": "same shape, status locked, p0 set" },
"last_round": { "...": "same shape, status settled or voided" },
"index": {"price": "65481.22", "at": "2026-07-21T04:19:58+00:00"},
// event-5m ONLY — the headline this round settles against. Every round
// also carries its own "subject_ref" (the pinned card_id), so settlement
// stays reproducible even after the featured headline rotates.
"subject": {
"card_id": "03cb7480-fb59-4023-9be9-da9ddb6b70b0",
"title": "Bispecific antibodies sweep into the clinic",
"pw_index": "100.0897"
}
}
Field notes. cadence_seconds is the round length — read it instead of inferring from the slug: event-5m is frozen for compatibility but runs a 900-second cadence, not 300. entries counts unique participants (one per user per round) — topping up does not increment it, and cancelling decrements it. entries_close_at is earlier than locks_at by entry_cutoff_seconds (anti-sniping); count down to that, not to the lock, or you will offer a form the server rejects. On BTC pools subject is absent and subject_ref is null.
/api/v1/btc-pool/enterEnter (or top up) a prediction in the currently-open round. Requires auth. Funds are debited from your USDC balance and escrowed until settlement. predicted_price must be > 0 and ≤ 100,000,000; amount_usdc must be between 0.10 and 1,000,000 (cumulative per user per round). A top-up must repeat the same predicted_price; a different one is rejected with 400 and your existing entry is left untouched. Topping up does not change entries, which counts unique participants.
curl -X POST https://cymetica.com/api/v1/btc-pool/enter \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"predicted_price": "65450.00", "amount_usdc": "1.00", "pool": "btc-5m"}'
{
"pool": "btc-5m",
"round_epoch": 5948760,
"predicted_price": "65450.00",
"amount_usdc": "1.00",
"pool_usdc": "1.20",
"entries": 3
}
/api/v1/btc-pool/cancelCancel your entry in the currently-open round — full refund of the escrowed stake, allowed strictly until lock. Requires auth. After cancelling you may re-enter the same round (at any price) while it is still open.
curl -X POST https://cymetica.com/api/v1/btc-pool/cancel \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"pool": "btc-5m"}'
{
"pool": "btc-5m",
"round_epoch": 5948760,
"refunded_usdc": "1.00",
"pool_usdc": "0.20",
"entries": 2
}
/api/v1/btc-pool/me?pool=btc-5mYour ticket in the open round, your all-time net profit in this pool (profit_usdc — terminal rounds only, so it accumulates as rounds settle), plus your last 20 settled/voided outcomes. Requires auth.
{
"enabled": true,
"pool": "btc-5m",
"profit_usdc": "0.10",
"entry": {"predicted_price": "65450.00", "amount_usdc": "1.00"},
"history": [
{
"round_epoch": 5948683,
"status": "settled",
"predicted_price": "65493.59",
"amount_usdc": "0.10",
"payout_usdc": "0.20",
"refund_usdc": null,
"p1": "65493.51",
"winning_price": "65493.59"
}
]
}
/api/v1/btc-pool/history?pool=btc-5m&limit=24Recent terminal rounds (settled or voided), newest first. Public. limit 1–200, default 24.
{
"pool": "btc-5m",
"rounds": [
{
"pool": "btc-5m",
"round_epoch": 5948683,
"status": "settled",
"p0": "65488.10",
"p1": "65493.51",
"winning_price": "65493.59",
"winning_distance": "0.08",
"winners_count": 1,
"entries": 2,
"pool_usdc": "0.20",
"fee_pct": "0"
}
]
}
WebSocket
One socket streams every pool — no polling, no auth, public aggregates only. Each message is {"type": …, "data": …, "ts": …}; state-scoped messages carry data.pool, so filter on it client-side.
wss://cymetica.com/ws/btc-pool
| Type | When | Payload (data) |
|---|---|---|
state | On connect (one per pool) and every ~10s | Full public state — same shape as GET /state |
index_tick | ~1s, when the BTC/USD index changes | {"price", "at", "index", "pools"} — always the shared BTC median: index is "btc-usd" and pools lists the slugs it settles (["btc-4h","btc-5m"]). Event pools never tick here; their index arrives inside state, which carries its own pool and index. Do not apply this tick to an event-pool chart. |
pool_update | Someone entered or cancelled in the open round | {"pool", "round_epoch", "entries", "pool_usdc"} plus the book delta: "rung" {"id", "price", "amount_usdc", "side"} on an entry (a repeat entry at the same price returns that rung's new cumulative amount_usdc), or "rung_removed" (the rung id) on a cancel. Apply the delta to the one matching rung — no need to re-fetch /state. |
round_update | A round locked / settled / voided | Round dict (same shape as rounds in /history) |
wins_recent | On connect | {"wins": […]} — recent anonymized wins (country-level, no PII) |
win_event | A round settles with a winner | One anonymized win — powers the global win map |
Keepalive: send the literal text ping → server replies pong. Idle sockets receive a JSON {"type": "ping"} every 60s.
Rate Limits
Standard platform rate limits apply per IP/account — see the X-RateLimit-* headers on REST responses. Entry is additionally bounded by the per-round cumulative stake cap ($1,000,000 per user per round).
Errors
Errors return {"detail": "…"} with a standard status code:
| Code | Meaning |
|---|---|
400 | Unknown pool, entries locked, stake outside $0.10–$1,000,000, insufficient balance, no entry to cancel |
401 | Missing/invalid JWT on /enter, /cancel, or /me |
503 | Pool not live ("This pool is not live yet") |