This document defines what the frontend client can expect from the PoolTool API: REST endpoints, WebSocket behavior, authentication, and data shapes. Base URL for REST is the API origin (e.g. https://api.pooltool.io). WebSocket URL is the same origin with path /ws (e.g. wss://api.pooltool.io/ws).
- Authentication is stake-key + password based. The login response includes a
tokenanduser_id. The client stores both (e.g.localStorage.pt_token,localStorage.pt_user_id) and sends the token on every request asAuthorization: Bearer <token>. - Protected endpoints (all
/api/user/*andPUT /api/admin_message/*) validate the token against theuser_tokenstable. User endpoints additionally verify that the token'suser_idmatches the path'suser_id— a user can only access their own data. Admin endpoints requireauthority = 'administrator'in theuserstable. - Unauthenticate (logout): There is no server-side logout or token revocation. The client unauthenticates by discarding the stored
tokenanduser_id(e.g. clearing localStorage/sessionStorage). No API call is required.
Endpoint: POST /auth/register
Request body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
stake_key |
string | Yes | Cardano stake key (hex or bech32) |
password |
string | Yes | User-chosen password |
Responses:
- 200:
{ "success": true, "user_id": "<uuid>" } - 409: Stake key already registered (body message)
Endpoint: POST /auth/login
Request body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
stake_key |
string | Yes | Registered stake key |
password |
string | Yes | Password |
Responses:
- 200:
{ "success": true, "user_id": "<uuid>", "token": "<url-safe-token>" }
The client should storeuser_idandtokenfor subsequent requests and for identifying the logged-in user. - 401: Unknown address or invalid password (body message)
Start verification: POST /auth/verify
Request body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
stake_key |
string | Yes | Stake key to verify |
user_id |
string | Yes | UUID of the user |
password |
string | Yes | User password |
Responses:
- 200:
- If already verified:
{ "status": "already_verified" } - If pending:
{ "status": "pending", "payment_address": "<addr>", "payment_amount": <lovelace> } - If new: same as pending; client must send exact
payment_amount(in lovelace) topayment_addressfrom the stake key to be verified.
- If already verified:
Check verification status: GET /auth/verify/{stake_key}
Responses:
- 200:
{ "status": "no_pending_verification" }{ "status": "pending", "payment_amount": <number> }{ "status": "verified", "user_id": "<uuid>" }
Start reset: POST /auth/reset_password
Request body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
stake_key |
string | Yes | Registered stake key |
new_password |
string | Yes | Desired new password |
Responses:
- 200:
{ "status": "pending", "payment_address": "<addr>", "payment_amount": <lovelace> }
Client must send exactlypayment_amounttopayment_addressfrom the stake key. Once the on-chain payment is detected, the password is updated automatically. - 404: No account found for this stake key.
Check reset status: GET /auth/check_reset/{stake_key}
Responses:
- 200:
{ "status": "no_pending_reset" }{ "status": "pending", "payment_amount": <number> }{ "status": "completed" }
Endpoint: POST /auth/queryaddress
Request body (JSON): { "stake_key": "<stake_key>" }
Responses:
- 200:
- Not found:
{ "success": false, "message": "Address not found" } - Found:
{ "success": true, "epoch": <number>, "amount": <number>, "delegatedTo": "<pool_id>", "delegatedToTicker": "<ticker>" }
- Not found:
All REST routes below are under the same origin. Prefix /api is used for most resources; /auth is used for auth (see §1). All responses are JSON unless noted.
| Method | Path | Description |
|---|---|---|
| GET | /api/pools |
List all non-retired pools (same shape as WebSocket pools snapshot). Each pool uses descriptive keys (see §4 Pool shape): pool_id, ticker, pool_name, live_stake, two_month_ros, one_month_ros, lifetime_ros, etc. |
| GET | /api/pool/{pool_id} |
Returns { "pool": <wire-only>, "pool_stats": {...} }. pool uses descriptive keys only (e.g. pool_id, ticker, pool_name, live_stake, two_month_ros). pool_stats includes reward_account, pool_owners, reward_address, owners, live_stake, and other detail fields. pool_id in path can be bech32 (pool1...) or hex. |
| GET | /api/pool/{pool_id}/history |
Pool history (blocks, assigned_slots, delegators_rewards, pool_fees, ros, stake). Query: limit (default 30). |
| GET | /api/pool/{pool_id}/blocks/{epoch} |
Blocks produced by pool in the given epoch. |
| GET | /api/pool/{pool_id}/awards |
Milestone awards (e.g. {"1": true, "10": true}). |
| GET | /api/tickers2026 |
Pool tickers lookup. Returns a JSON object mapping pool IDs to ticker/name/group data. Served from DB (populated by periodic tickers task and uploaded to S3). |
| Method | Path | Description |
|---|---|---|
| GET | /api/blocks/{epoch} |
Blocks for epoch in competitive-block format: { blockHeight: { poolId: { hash: { ...blockData } } } }. Optional query params: limit, block_height. |
| GET | /api/recent_block |
Latest block (single object). |
| Method | Path | Description |
|---|---|---|
| GET | /api/epoch/{epoch} |
Epoch data (epoch_blocks, last_block_time, etc.). |
| GET | /api/epoch_params/{epoch} |
Epoch protocol parameters: monetary_expansion_rate, treasury_growth_rate, decentralisation, influence, max_bh_size, max_block_size, etc. |
| GET | /api/livedata |
Current-epoch params (livedata): same shape as epoch_params for latest epoch — monetary_expansion_rate, treasury_growth_rate, decentralisation, influence, etc. |
| GET | /api/active_stake/{epoch} |
Total active stake for epoch: { "epoch": <n>, "total_active_stake": <n> }. |
| GET | /api/epoch_exchange_rates |
Exchange rates by epoch: { "<epoch>": <value>, ... }. |
| Method | Path | Description |
|---|---|---|
| GET | /api/ecosystem |
Ecosystem summary: total_staked, active_pools, delegators, rewardpot, reserves, epochLength (432000), activeSlotCoeff (0.05), and optionally maxLiveStake if present in DB. |
| GET | /api/syncdata |
Sync status: keyed { "<key>": { "block": <n>, "bool": <bool> }, ... } plus numeric syncd, samples, majoritymax (always numbers for reporter ratio and live max tip). |
| GET | /api/heights |
Per-pool tip heights from live_sync_stats. Returns a JSON object keyed by pool identifier with height values. Also available as { "latest_block": <number> } when only a single value is present. |
| Method | Path | Description |
|---|---|---|
| GET | /api/stake_hist/{address} |
Stake history for a stake key (address). Loads from S3 archive and optionally builds recent epochs from DB. Returns a list of epoch records. |
| POST | /api/pivotrewards |
Trigger pivot calculation. Body: { "stake_keys": ["<key1>", ...] }. Max 5 keys. Returns { "status": "processing", "keys": [...], "message": "Results will be pushed via WebSocket..." }. Subscribe to WebSocket channel stake_hist (with param address = stake key) before calling; results are pushed as a snapshot when ready (usually within seconds). |
Endpoints use user_id in the path. The client must send Authorization: Bearer <token> (the token from login). The server validates the token and verifies the token's user matches the path user — users can only access their own data.
| Method | Path | Description |
|---|---|---|
| GET | /api/user/{user_id} |
Full user profile: user_id, created_at, authority, myAddresses, myPools, myApiKeys, settings, favorites, userType. |
| PUT | /api/user/{user_id}/settings |
Update settings/favorites. Body: { "settings": {...}, "favorites": ["pool_id", ...] } (both optional). |
| POST | /api/user/{user_id}/favorites |
Add favorite. Body: { "pool_id": "<pool_id>" }. |
| DELETE | /api/user/{user_id}/favorites/{pool_id} |
Remove favorite. |
| Method | Path | Description |
|---|---|---|
| GET | /api/translations |
List of available locale codes. |
| GET | /api/translations/{locale} |
Translation data for locale (JSON object). |
| GET | /api/languages |
Languages metadata object keyed by locale. |
| Method | Path | Description |
|---|---|---|
| GET | /api/admin_message |
Current admin banner for web (no auth). Returns { "chillin": <bool>, "title": "<string>", "message": "<string>" }. chillin is true when no message is set. |
| GET | /api/admin_message/{target} |
Admin banner for a specific target (e.g. web). |
| PUT | /api/admin_message/{target} |
Set/clear admin banner. Requires admin token (Authorization: Bearer <token> from a user with authority = "administrator"). Body: { "title": "<string>", "message": "<string>" }. Send empty strings to clear. |
These endpoints manage Firebase Cloud Messaging (FCM) tokens and notification subscriptions for the iOS and Android apps. No authentication is required — the FCM token itself acts as the device identity.
All endpoints are under /api/fcm. The pool_id field accepts either hex (abc123...) or bech32 (pool1...) format; the server converts bech32 to hex internally.
Endpoint: POST /api/fcm/register
Registers or re-registers a device's FCM token. If the token already exists (e.g. after app reinstall or token refresh), it updates the platform and resets the invalid flag to false.
Request body (JSON):
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
token |
string | Yes | — | FCM device token |
platform |
string | No | "unknown" |
"android", "ios", or "unknown" |
Responses:
- 200:
{ "status": "ok" } - 422:
token is required(empty/whitespace-only token)
Notes:
- If a token was previously marked invalid (due to failed sends), re-registering via this endpoint resets it to valid.
- Platform values other than
android,ios, orunknownare silently normalized tounknown.
Endpoint: POST /api/fcm/subscribe
Subscribe a device to push notifications for a specific pool and alert type. If the token is not yet registered, it is auto-created. If the subscription already exists, its config is updated.
Request body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
token |
string | Yes | FCM device token |
pool_id |
string | Yes | Pool ID (hex or bech32) |
alert_type |
string | Yes | One of: block_production, fee, saturation, pledge |
config |
object | No | Optional JSON config for this subscription (default {}) |
Responses:
- 200:
{ "status": "ok" } - 400:
Token was invalidated; re-register with POST /api/fcm/register— the token was marked invalid by a failed send. The client must call/api/fcm/registerfirst to re-validate it. - 422: Validation error (empty token, invalid
alert_type)
Alert types and their triggers:
alert_type |
When notification is sent | Currently active |
|---|---|---|
block_production |
Pool mints a new block (within 120s of block time — "live" blocks only) | Yes |
fee |
Pool changes its margin (fee), fixed cost, or pledge via a pool update cert | Yes |
saturation |
Pool's live stake exceeds the saturation threshold, checked at each epoch boundary | Yes |
pledge |
Reserved for pledge violation alerts (subscriptions accepted, notifications not yet wired) | No (stored only) |
Endpoint: PUT /api/fcm/subscribe
Updates the config JSON for an existing subscription. Returns 404 if no matching subscription exists.
Request body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
token |
string | Yes | FCM device token |
pool_id |
string | Yes | Pool ID (hex or bech32) |
alert_type |
string | Yes | One of: block_production, fee, saturation, pledge |
config |
object | Yes | New config JSON to set |
Responses:
- 200:
{ "status": "ok" } - 404:
Subscription not found - 422: Invalid
alert_type
Endpoint: DELETE /api/fcm/subscribe
Removes a subscription. If alert_type is provided, removes only that specific subscription. If alert_type is omitted, removes all subscriptions for the token + pool combination.
Request body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
token |
string | Yes | FCM device token |
pool_id |
string | Yes | Pool ID (hex or bech32) |
alert_type |
string | No | Specific alert type to remove, or omit for all |
Responses:
- 200:
{ "status": "ok" }(always 200, even if nothing was deleted) - 422: Invalid
alert_type(if provided and not in allowed set)
Endpoint: DELETE /api/fcm/subscribe/pool
Convenience endpoint: removes all subscriptions for a token + pool pair. Equivalent to calling DELETE /api/fcm/subscribe without alert_type.
Request body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
token |
string | Yes | FCM device token |
pool_id |
string | Yes | Pool ID (hex or bech32) |
Responses:
- 200:
{ "status": "ok" }
Endpoint: GET /api/fcm/subscriptions
Returns all active subscriptions for a device token.
Query parameters:
| Param | Type | Required | Description |
|---|---|---|---|
token |
string | Yes | FCM device token |
pool_id |
string | No | Filter to a specific pool (hex or bech32) |
Responses:
- 200: Array of subscription objects:
Returns an empty array
[ { "pool_id": "abc123...", "alert_type": "block_production", "config": {} }, { "pool_id": "abc123...", "alert_type": "fee", "config": {} } ][]if no subscriptions exist. - 422:
token is required
Endpoint: POST /api/fcm/unregister
Permanently removes a device token and all of its subscriptions (cascade delete). Use when the user logs out of the app or disables notifications entirely.
Request body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
token |
string | Yes | FCM device token |
Responses:
- 200:
{ "status": "ok" }(always 200, even if token didn't exist) - 422:
token is required
The server automatically manages token validity:
- On every send: When the server sends a push notification, Firebase reports which tokens are permanently invalid (unregistered device, sender mismatch). These tokens are marked
invalid = truein the database. - Invalid tokens are excluded: Subsequent notification sends skip tokens marked invalid. No unnecessary FCM traffic is generated.
- Re-registration resets validity: If a device gets a new FCM token (token refresh) or reinstalls the app, calling
POST /api/fcm/registerwith the new (or same) token resetsinvalidtofalse. - Subscribing with an invalid token is rejected:
POST /api/fcm/subscribereturns HTTP 400 if the token is currently marked invalid. The client should call/api/fcm/registerfirst.
Recommended app flow:
- On app startup, call
POST /api/fcm/registerwith the current FCM token and platform. - Call
GET /api/fcm/subscriptions?token=...to load the user's current subscriptions. - When the user adds/removes a pool from their watch list, call
POST /api/fcm/subscribeorDELETE /api/fcm/subscribeaccordingly. - When the FCM SDK fires a token-refresh callback, call
POST /api/fcm/registerwith the new token. Note: subscriptions are tied to the old token — the app should re-subscribe with the new token and unregister the old one. - On logout or "disable notifications," call
POST /api/fcm/unregister.
Push notifications sent by the server have the following structure:
Android (via AndroidConfig):
notification.click_action:"FLUTTER_NOTIFICATION_CLICK"notification.title/notification.body: Alert text
iOS (via APNSConfig):
aps.alert.title/aps.alert.body: Alert textaps.sound:"default"
Data payload (all platforms):
click_action:"FLUTTER_NOTIFICATION_CLICK"poolId: The hex pool ID that triggered the notification- Additional keys depending on the notification type may be added in the future.
| Method | Path | Description |
|---|---|---|
| GET | /health |
{ "status": "ok", "latest_block": <n>, "ws_clients": <n>, "ws_subscriptions": <n>, "periodic_tasks": {...} }. |
- POST
/ouraconsume— Oura pipeline webhook; not for browser clients. Receives parsed chain events (Block, Transaction, TxInput, TxOutput, BlockEnd, StakeDelegation, StakeDeregistration, PoolRegistration, PoolRetirement, MoveInstantaneousRewardsCert).
- URL: Same origin as REST, path
/ws(e.g.wss://api.pooltool.io/ws). - Protocol: Standard WebSocket. Messages are UTF-8 JSON text.
- Connection: No query parameters or headers are required for basic connect. The server accepts the connection and does not require auth for the WebSocket itself; user-specific data is requested via the
user_datachannel withparams.user_id.
Every client message must be a JSON object with an action field. Optional fields: channel, params.
| Action | When to use | channel |
params |
|---|---|---|---|
subscribe |
Subscribe to a channel (and optionally get initial snapshot). | Required. Channel name (see §3.4). | Optional. Object used to scope the channel (e.g. pool_id, epoch, address, user_id). |
unsubscribe |
Stop receiving a channel. | Required. Same as used in subscribe. | Optional. Must match the params used when subscribing. |
pong |
Response to server ping (keepalive). |
— | — |
Examples:
{ "action": "subscribe", "channel": "pools" }
{ "action": "subscribe", "channel": "pool_stats", "params": { "pool_id": "abc123" } }
{ "action": "subscribe", "channel": "user_data", "params": { "user_id": "uuid-here" } }
{ "action": "unsubscribe", "channel": "pool_stats", "params": { "pool_id": "abc123" } }
{ "action": "pong" }Invalid JSON or unknown action results in a server message { "type": "error", "msg": "..." }.
All server messages are JSON objects with at least:
type— One of:ping,snapshot,update,error.
Ping (keepalive):
{ "type": "ping", "t": <unix_ts> }
Client should reply with { "action": "pong" }. If the client does not respond in time (see §3.5), the server closes the connection.
Snapshot (initial / full state):
{ "type": "snapshot", "channel": "<channel>", "params": <params>, "seq": <number>, "data": <payload> }
Sent once per subscription when the client subscribes (if the channel has a fetcher). This is the raw data after connect or reconnect: the client subscribes to the channels it needs, and the server immediately sends a snapshot for each. No separate "download raw data" call is needed — subscribing is the way to get initial state.
Update (incremental / live):
{ "type": "update", "channel": "<channel>", "params": <params>, "seq": <number>, "data": <payload> }
Pushed when backend updates that channel (e.g. new block, pool metadata, user settings). Payload shape is channel-specific; it may be a full replacement or a delta depending on the channel.
Error:
{ "type": "error", "msg": "<string>" }
Subscription key = channel + optional params (canonical form: channel:param1_value:param2_value). Params are used to scope the channel (e.g. which pool, which epoch). Below, "Params" are the keys the client can send in params when subscribing; the server uses them to fetch the snapshot and to match broadcasts.
| Channel | Params | Snapshot on subscribe | Live updates | Typical snapshot payload |
|---|---|---|---|---|
pools |
— | Yes | Yes (full list or per-pool updates) | List of pool objects with descriptive keys (see §4): pool_id, ticker, pool_name, live_stake, two_month_ros, one_month_ros, lifetime_ros, etc. |
pool_stats |
pool_id |
Yes | Yes | Single pool object (descriptive keys) + extra fields: description, metadata, relay_details, reward_account, pool_owners, live_stake, two_month_ros, one_month_ros, lifetime_ros, etc. |
pool_history |
pool_id, optional limit |
Yes | No | { blocks, assigned_slots, delegators_rewards, pool_fees, ros, stake } by epoch. |
recent_block |
— | Yes | Yes (pushed by Oura consume_block_end) |
Single latest block object. |
epoch_data |
optional epoch |
Yes | Yes (pushed on new block via block_competitive pg_notify and periodic ecosystem broadcast) |
Epoch summary (epoch_blocks, last_block_time, etc.). |
epoch_params |
optional epoch |
Yes | No | Epoch protocol parameters. |
ecosystem |
— | Yes | Yes (broadcast every 60s and on epoch transitions) | Ecosystem summary object. |
active_stake |
optional epoch |
Yes | No | { epoch, total_active_stake }. |
blocks |
optional epoch, limit, block_height |
Yes | Yes (pushed via block_competitive pg_notify) |
Competitive-block structure for that epoch. |
pool_blocks |
pool_id, optional epoch |
Yes | No | List of block records for pool in epoch. |
stake_hist |
address (stake key) |
Yes | Yes (after pivot or updates) | List of epoch records or pivot result. |
epoch_exchange_rates |
— | Yes | No | { "<epoch>": <value>, ... }. |
syncdata |
— | Yes | Yes (pushed via syncdata_updated pg_notify from tipsApiApp) |
Keyed { "<key>": { "block", "bool" }, ... } plus numeric syncd, samples, majoritymax. |
heights |
— | Yes | Yes (pushed via heights_updated pg_notify from tipsApiApp) |
Per-pool tip heights object. |
awards |
pool_id |
Yes | No | { "1": true, "10": true, ... }. |
user_data |
user_id |
Yes | Yes (e.g. after settings update) | User profile (myAddresses, myPools, settings, favorites, userType, etc.). |
admin_message |
— | Yes | Yes (after admin sets/clears message) | { "chillin": <bool>, "title": "<string>", "message": "<string>" }. |
- Heartbeat: Server sends
pingat a configurable interval (default 30s). Client must respond withpong. If the server does not receive apongwithin the heartbeat timeout (default 10s after the ping interval), it closes the connection. - Disconnect: Client disconnects by closing the WebSocket (e.g.
ws.close()). No "goodbye" message is required. On disconnect, the server clears all subscriptions for that connection. - Reconnect: After a reconnect, the client must send
subscribeagain for each channel it needs; the server will send a fresh snapshot for each, providing the raw data for that channel. There is no server-side session or "resume subscriptions" — re-subscribe after every connect.
- Connect: Open WebSocket → send one or more
subscribemessages → receivesnapshotmessages with full state for each channel. - Reconnect: Same: open a new WebSocket, then send the same
subscribemessages as before; the server sends snapshots again. The client does not call a separate "download raw data" REST endpoint for WebSocket state — the snapshots are the raw data for the subscribed channels.
For REST-only raw data (e.g. full pool list, full epoch blocks), use the REST endpoints in §2; they return the same data as the corresponding WebSocket snapshots.
Pool list (GET /api/pools, WS pools) and pool object (GET /api/pool/{id} → pool, WS pool_stats) use descriptive key names only. No compressed/short keys.
| Key | Type | Description |
|---|---|---|
pool_id |
string | Pool identifier (hex). |
ticker |
string | Pool ticker symbol. |
pool_name |
string | Display name. |
group_name |
string | Group name (optional). |
online_relays |
number | Count of online relays. |
offline_relays |
number | Count of offline relays. |
cost |
number | Fixed cost (lovelace). |
margin |
number | Margin (percentage, 0–100). |
pledge |
number | Pledge (lovelace). |
pool_pledge_value |
number | Pool pledge value. |
future_pledge |
number | null | Pending pledge. |
future_pledge_epoch |
number | null | Epoch for future pledge. |
future_cost |
number | null | Pending cost. |
future_cost_epoch |
number | null | Epoch for future cost. |
future_margin |
number | Pending margin (%). |
future_margin_epoch |
number | null | Epoch for future margin. |
itn_verified |
boolean | ITN verified flag. |
epoch_blocks |
number | Blocks in current epoch. |
epoch_blocks_epoch |
number | null | Epoch of epoch_blocks. |
life_blocks |
number | Lifetime blocks. |
retired |
boolean | Pool retired. |
genesis |
boolean | Genesis pool. |
rank |
number | Rank. |
assigned_slots |
number | Assigned slots (current). |
assigned_slots_epoch |
number | null | Epoch of assigned_slots. |
imposter |
any | null | Imposter flag. |
lifetime_per_blocks |
number | Lifetime blocks (per). |
lifetime_per_slots |
number | Lifetime slots (per). |
delegator_count |
number | Number of delegators. |
future_retired |
boolean | Pending retirement. |
future_retired_epoch |
number | null | Epoch for future retirement. |
live_stake |
number | Active stake (lovelace). |
protocol_major |
number | Protocol major version. |
protocol_minor |
number | Protocol minor version. |
two_month_ros |
number | null | ROS over last ~2 months (12 epochs). |
one_month_ros |
number | null | ROS over last ~1 month (6 epochs). |
lifetime_ros |
number | null | Lifetime ROS. |
Previously the API used short keys to save bandwidth. These are no longer sent; the table below is for migration reference only.
| Old key | New key |
|---|---|
id |
pool_id |
t |
ticker |
n |
pool_name |
g |
group_name |
o |
online_relays |
oo |
offline_relays |
f |
cost |
m |
margin |
p |
pledge |
ap |
pool_pledge_value |
fp |
future_pledge |
fpe |
future_pledge_epoch |
ff |
future_cost |
ffe |
future_cost_epoch |
fm |
future_margin |
fme |
future_margin_epoch |
i |
itn_verified |
b |
epoch_blocks |
eb |
epoch_blocks_epoch |
l |
life_blocks |
d |
retired |
x |
genesis |
r |
rank |
z |
assigned_slots |
ez |
assigned_slots_epoch |
xx |
imposter |
zl |
lifetime_per_blocks |
zs |
lifetime_per_slots |
dc |
delegator_count |
fd |
future_retired |
fde |
future_retired_epoch |
bs |
(removed; use live_stake only) |
live_stake |
live_stake |
pm |
protocol_major |
pn |
protocol_minor |
tr |
two_month_ros |
sr |
one_month_ros |
lros |
lifetime_ros |
- Pool stats (detail): Returned under
pool_statson GET/api/pool/{pool_id}. Includesreward_account,pool_owners,reward_address,owners,live_stake,description,homePage,metadataHash,metadataUrl,relay_details,extended_json,firstEpoch,public_note,pooltoolbot_subscribers. The same pool's wire object (descriptive keys) is returned aspool. - Block (recent / list):
block,slot,epoch,epoch_slot,hash,pool_id,pool_ticker,timestamp,transactions,fees,output,body_size, and in competitive view:leaderPoolId,leaderPoolTicker,time,chained,competitive,slotbattle, etc. - Epoch data:
epoch,epoch_blocks,epoch_feess,last_block_time,epoch_tx_count,epoch_output,expected_blocks. - Epoch params / livedata:
epoch,influence,max_bh_size,max_block_size,max_epoch,monetary_expansion_rate,optimal_pool_count,protocol_major,protocol_minor,treasury_growth_rate,decentralisation,treasury,reserves,reward_pot,epoch_feess. Use GET/api/epoch_params/{epoch}or GET/api/livedata(current epoch) formonetary_expansion_rate,treasury_growth_rate,decentralisation,influence. - Ecosystem:
current_epoch,total_utxo,saturation,saturated,optimal_pools,treasury,reserves,rewardpot,total_staked,active_pools,delegators,epochLength(number),activeSlotCoeff(number), and optionallymaxLiveStakewhen present. - Syncdata: Keyed entries
{ "<key>": { "block": <n>, "bool": <bool> }, ... }plus numericsyncd(count of reporters in sync),samples(total reporter count),majoritymax(max reported block height; use for "live max tip" or fall back torecent_block.block). - User:
user_id,created_at,authority,myAddresses,myPools,myApiKeys,settings,favorites,userType. - Stake history (per epoch):
epoch,amount,operator_rewards,stake_rewards,delegated_to_pool,delegated_to_ticker,forecast; pivot result addslifeAmount,lifeOperatorRewards,lifeStakeRewards,operatorRewards,stakeRewards,rewardsSentTo,rewardAddrDetails,epochsStaked.
Exact field semantics and types can be inferred from the backend fetchers and REST handlers; this spec gives the frontend a single place for expected endpoints, flows, and payload roles.
This section documents how live data reaches the frontend, including the tipsApiApp integration.
tipsApiApp is a separate FastAPI process (the original tips/slots processor) that handles pool operator tip reports. It writes to Postgres and fires pg_notify events that the PoolTool API listens for:
| pg_notify channel | WS channel updated | Source |
|---|---|---|
syncdata_updated |
syncdata |
tipsApiApp writes sync stats to live_sync_stats |
heights_updated |
heights |
tipsApiApp writes per-pool heights to live_sync_stats |
block_competitive |
blocks, epoch_data |
tipsApiApp writes competitive block analysis |
Oura follows the chain at tip-6 and sends parsed events to POST /ouraconsume. The chain consumer updates:
| Event | WS channels updated | FCM push notifications |
|---|---|---|
| Block + BlockEnd | recent_block, blocks, pool_blocks, epoch_data |
block_production (live blocks only) |
| PoolRegistration | pools, pool_stats |
fee (pledge/cost/margin changes) |
| PoolRetirement | pools |
— |
| StakeDelegation | (DB only) | — |
| Epoch transition | ecosystem, epoch_params, active_stake, pool_stats (all pools) |
saturation (saturated pools) |
| Task | Interval | WS channels affected |
|---|---|---|
ecosystem_broadcast |
60s | ecosystem |
process_orphans |
20s | (updates DB; blocks reflects changes on next query) |
battle_data_s3 |
30s | (S3 only) |
update_metadata |
1h | pools (via DB) |
update_relays |
1h | pools (via DB) |
battle_trends |
1h | (S3 only) |
check_pledge |
24h | (Telegram notifications) |
update_tickers |
6h | (S3 + DB) |
exchange_rates |
1h | (DB; epoch_exchange_rates reflects on next query) |
| Concern | Behavior |
|---|---|
| Authenticate | POST /auth/login with stake_key + password → store user_id and token. Send Authorization: Bearer <token> on all subsequent requests. |
| Unauthenticate | Client discards token and user_id; no API call. |
| Reset password | POST /auth/reset_password with stake_key + new_password → send ADA to returned address → poll GET /auth/check_reset/{stake_key} until completed. |
| Subscribe to events | WebSocket to /ws → send { "action": "subscribe", "channel": "<name>", "params": {...} } → receive snapshot then update messages. |
| Raw data after connect/reconnect | Send subscribe for each channel; server replies with snapshot containing full state. No separate download endpoint. |
| Disconnect | Close WebSocket. Server cleans up subscriptions. |
| Reconnect | Open new WebSocket, send same subscribe messages again to get new snapshots. |
| REST | All read endpoints are GET unless noted; user mutations use PUT/POST/DELETE with user_id in path. |
| FCM push (mobile) | On app start: POST /api/fcm/register with device token. Subscribe to pools: POST /api/fcm/subscribe. List subs: GET /api/fcm/subscriptions?token=.... On token refresh: register new token, re-subscribe, unregister old. On logout: POST /api/fcm/unregister. |
| CORS | All origins allowed; credentials allowed. |
| API docs | Interactive docs at /docs (FastAPI Swagger). |