NEW: Live arbitrage across 10+ prediction markets.Arbitrage →
← Index
APIOct 18, 2026

How to Build a Prediction Market Dashboard With an API (2026)

How to Build a Prediction Market Dashboard With an API (2026)

The Short Answer

You can build a working prediction market dashboard in about 40 lines of Node.js: pull the most active Polymarket markets from the Gamma API and a few Kalshi series from Kalshi's market API, render them as a table, and refresh every minute. Both APIs are free and need no key for market data. The full script is below, tested live. To go beyond two venues without writing a parser for each, the Predictefy API returns markets from 15+ venues in one schema, so the dashboard code stays the same as it grows.

Key Takeaways

  • A useful dashboard needs four things per market: venue, title, current Yes probability and 24-hour volume.
  • Polymarket's Gamma API can sort by 24-hour volume; Kalshi's market API filters by series, such as KXFEDDECISION.
  • Render on the server and cache the last good page, so one failed API call never blanks the screen.
  • Refresh once a minute for a dashboard; use WebSockets only for screens that must react within seconds.
  • Swap the two venue loaders for one Predictefy call to show 15+ venues with the same table code.

What should a prediction market dashboard show?

Start small. Per market: the venue, the question, the current Yes probability and the 24-hour volume. That answers "what is moving and what does the market think" at a glance. Later additions are easy once the data flows: price change over 24 hours, spread, close date and links to each market.

How do you build it?

This is a complete Node 18+ dashboard with no dependencies. It serves an HTML table on port 3000 and refreshes the data every minute:

import { createServer } from 'node:http';

const pct = (p) => (p == null || Number.isNaN(p) ? '-' : `${(p * 100).toFixed(1)}%`);
const escapeHtml = (s) => String(s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));

async function loadRows() {
  // Polymarket: the 10 most active open markets
  const pm = await (await fetch('https://gamma-api.polymarket.com/markets?limit=10&active=true&closed=false&order=volume24hr&ascending=false')).json();
  const pmRows = pm.map((m) => ({
    venue: 'Polymarket',
    title: m.question,
    yes: Number(JSON.parse(m.outcomePrices)[0]),
    volume: Math.round(m.volume24hr ?? 0),
  }));

  // Kalshi: open markets in a few series you care about
  const series = ['KXFEDDECISION', 'KXNFLGAME'];
  const kRows = [];
  for (const s of series) {
    const { markets } = await (await fetch(`https://external-api.kalshi.com/trade-api/v2/markets?series_ticker=${s}&status=open&limit=5`)).json();
    for (const m of markets) {
      const bid = Number(m.yes_bid_dollars), ask = Number(m.yes_ask_dollars);
      kRows.push({ venue: 'Kalshi', title: `${m.title} ${m.yes_sub_title ?? ''}`.trim(), yes: (bid + ask) / 2, volume: Math.round(Number(m.volume_24h_fp ?? 0)) });
    }
  }
  return [...pmRows, ...kRows];
}

let html = '<p>Loading...</p>';
async function refresh() {
  try {
    const rows = await loadRows();
    const body = rows.map((r) => `<tr><td>${escapeHtml(r.venue)}</td><td>${escapeHtml(r.title)}</td><td>${pct(r.yes)}</td><td>${r.volume.toLocaleString()}</td></tr>`).join('');
    html = `<meta http-equiv="refresh" content="60"><h1>Prediction markets</h1><p>Updated ${new Date().toISOString()}</p>
<table border="1" cellpadding="6"><tr><th>Venue</th><th>Market</th><th>Yes</th><th>24h volume</th></tr>${body}</table>`;
  } catch (e) {
    console.error('refresh failed', e.message); // keep the last good page
  }
}

await refresh();
setInterval(refresh, 60_000);
createServer((req, res) => { res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.end(html); }).listen(3000);
console.log('Dashboard on http://localhost:3000');

Save it as dashboard.mjs, run node dashboard.mjs and open http://localhost:3000. In our checks on 16 October 2026 it served a table led by a Bundesliga match, an Indiana data center bill at 12.5% and the Brazilian presidential election at 21.5%, followed by the Kalshi Fed and NFL markets. Polymarket's outcomePrices is a JSON-encoded string, so it is parsed before use. Kalshi publishes a bid and an ask, and the script shows their midpoint. Fetching on the server keeps keys and rate limits off the browser, and the try block keeps the last good table if an API call fails.

How do you make it better?

  • Pick your markets. Replace "most active" with a watchlist of slugs and tickers you follow.
  • Add change. Store each refresh and show the move since yesterday, or pull history from each venue's price history endpoint.
  • Escape titles before inserting them into HTML if you add markets from sources you do not control.
  • Mind rate limits. One refresh a minute is light; see Polymarket and Kalshi limits before you poll faster.
  • Go live. For a trading screen that must react within seconds, stream books over a WebSocket instead of polling.

How do you add more venues without more code?

Every new venue in the script above means a new loader that knows that venue's field names and price format. With Predictefy, one loader covers 15+ venues, because every market comes back in the same shape:

import Predictefy from '@predictefy/sdk';

const client = new Predictefy({ apiKey: process.env.PREDICTEFY_API_KEY });

async function loadRows() {
  const markets = await client.router.fetchMarkets({ query: 'election', status: 'active', limit: 25 });
  return markets.map((m) => ({
    venue: m.url ? new URL(m.url).hostname : "",
    title: m.title,
    yes: m.outcomes[0]?.price ?? null,
    volume: Math.round(m.volume24h ?? 0),
  }));
}

This type-checks against @predictefy/sdk 1.0.0-beta.11 and drops straight into the dashboard in place of the two venue loaders. A market search costs 1 credit, so a refresh a minute uses about 43,000 credits a month, which fits Builder's 250,000. On Builder, bulk routes and live streams (20 on Builder) let the same dashboard cover far more markets for the same cost.

Frequently Asked Questions

Do I need an API key to build a prediction market dashboard?

Not for Polymarket or Kalshi market data, which are free and keyless. A Predictefy key, free to start, adds 15+ venues in one schema.

How often should a dashboard refresh?

Once a minute suits most dashboards. If the screen must react within seconds, use a WebSocket stream instead of polling.

Should I call the APIs from the browser?

Fetch on a server, as in the script above. It keeps keys private, lets you cache, and serves every viewer from one set of API calls.

How do I show the most active markets?

On Polymarket, sort Gamma's /markets with order=volume24hr&ascending=false. On Kalshi, read volume_24h_fp from each market and sort by it.

Can I add more venues later?

Yes. Either write a loader per venue, or switch the loader to Predictefy's fetchMarkets, which returns 15+ venues in one shape.