> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dualmindlab.tech/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> DualMind Arena API base URL: https://api.dualmindlab.tech. Public endpoints only; admin routes are not part of this API.
> Authenticate with the access token from an Arena session (a Supabase JWT) as `Authorization: Bearer <token>`. There are no API keys yet; never invent one.
> For battles call POST /api/arena/dualchat, then vote with POST /api/arena/model-vote using the returned comparisonId. Keep model names hidden from voters until they vote.
> GET /api/arena/model-stats (leaderboard) and GET /health need no token.

# Leaderboard

> How DualMind turns votes into an ELO ranking, and how to read every number on the board.

export const LiveLeaderboard = ({limit = 10, endpoint = "https://api.dualmindlab.tech/api/arena/model-stats"}) => {
  const [items, setItems] = useState(null);
  const [error, setError] = useState(null);
  const [provider, setProvider] = useState("all");
  const [minBattles, setMinBattles] = useState(50);
  const current = useRef(null);
  const load = useCallback(async () => {
    if (current.current) current.current.abort();
    const ctrl = new AbortController();
    current.current = ctrl;
    let timedOut = false;
    const timer = setTimeout(() => {
      timedOut = true;
      ctrl.abort();
    }, 15000);
    setError(null);
    setItems(null);
    try {
      const res = await fetch(endpoint, {
        signal: ctrl.signal
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const body = await res.json();
      if (current.current !== ctrl) return;
      setItems(Array.isArray(body.items) ? body.items : []);
    } catch (err) {
      if (current.current !== ctrl) return;
      setError(timedOut ? "The request timed out." : "Couldn't reach the API.");
    } finally {
      clearTimeout(timer);
    }
  }, [endpoint]);
  useEffect(() => {
    load();
    return () => {
      if (current.current) current.current.abort();
    };
  }, [load]);
  const providers = {
    groq: "Groq",
    google: "Google",
    cloudflare: "Cloudflare"
  };
  const rows = useMemo(() => {
    if (!items) return [];
    return items.filter(m => provider === "all" || m.providerName === provider).filter(m => (m.totalResponses || 0) >= minBattles).sort((a, b) => (b.eloScore || 0) - (a.eloScore || 0)).slice(0, limit);
  }, [items, provider, minBattles, limit]);
  const [lo, hi] = useMemo(() => {
    if (!rows.length) return [900, 1200];
    const scores = rows.map(r => r.eloScore || 0);
    return [Math.min(...scores) - 20, Math.max(...scores)];
  }, [rows]);
  return <div className="dm-widget not-prose" role="group" aria-label="Live leaderboard">
      <div className="dm-widget-head">
        <div>
          <span className="dm-label">Live data</span>
          <p className="dm-widget-title">Arena leaderboard</p>
        </div>
        <div style={{
    display: "flex",
    flexWrap: "wrap",
    gap: "0.5rem"
  }}>
          <select className="dm-ctl" value={provider} onChange={e => setProvider(e.target.value)} aria-label="Filter by provider">
              <option value="all">All providers</option>
              <option value="groq">Groq</option>
              <option value="google">Google AI Studio</option>
              <option value="cloudflare">Cloudflare Workers AI</option>
            </select>
          <select className="dm-ctl" value={minBattles} onChange={e => setMinBattles(Number(e.target.value))} aria-label="Minimum number of battles">
              <option value={0}>Any battles</option>
              <option value={20}>20+ battles</option>
              <option value={50}>50+ battles</option>
              <option value={100}>100+ battles</option>
            </select>
          <button type="button" className="dm-ctl" onClick={load}>
            Refresh
          </button>
        </div>
      </div>

      {error && <p role="alert" style={{
    margin: 0
  }}>
          {error} Try again, or call <code>GET /api/arena/model-stats</code> yourself.
        </p>}

      {!error && !items && <div aria-busy="true" aria-label="Loading leaderboard" style={{
    display: "grid",
    gap: "0.7rem"
  }}>
          {[0, 1, 2, 3, 4].map(i => <div key={i} className="dm-skeleton" style={{
    width: `${92 - i * 9}%`
  }} />)}
        </div>}

      {!error && items && <div className="dm-table-wrap">
          <table className="dm-table">
            <caption className="dm-muted" style={{
    fontSize: "0.8rem"
  }}>
              Top {rows.length} of {items.length} ranked models, by ELO. Rank is the overall rank.
            </caption>
            <thead>
              <tr>
                <th scope="col">Rank</th>
                <th scope="col">Model</th>
                <th scope="col">ELO</th>
                <th scope="col" style={{
    textAlign: "right"
  }}>Win rate</th>
              </tr>
            </thead>
            <tbody>
              {rows.map(m => <tr key={m.modelId || m.modelName}>
                  <td>#{m.eloRank}</td>
                  <td style={{
    maxWidth: "16rem"
  }}>
                    <span style={{
    fontWeight: 600,
    overflowWrap: "anywhere"
  }}>{m.displayName || m.modelName}</span>
                    <div className="dm-muted" style={{
    fontSize: "0.75rem"
  }}>
                      {providers[m.providerName] || m.providerName} · {m.totalResponses} battles · {m.totalWins}W {m.totalLosses}L {m.totalTies}T
                    </div>
                  </td>
                  <td style={{
    minWidth: "8rem"
  }}>
                    <div style={{
    display: "flex",
    alignItems: "center",
    gap: "0.5rem"
  }}>
                      <span>{Math.round(m.eloScore)}</span>
                      <div className="dm-bar" aria-hidden="true" style={{
    flex: 1
  }}>
                        <span style={{
    width: `${Math.max(6, (m.eloScore - lo) / Math.max(1, hi - lo) * 100)}%`
  }} />
                      </div>
                    </div>
                  </td>
                  <td style={{
    textAlign: "right"
  }}>{Number(m.winRate || 0).toFixed(1)}%</td>
                </tr>)}
              {rows.length === 0 && <tr>
                  <td colSpan={4} className="dm-muted">
                    No models match these filters.
                  </td>
                </tr>}
            </tbody>
          </table>
        </div>}
    </div>;
};

export const EloCalculator = ({ratingA = 1050, ratingB = 980, k = 32}) => {
  const [ra, setRa] = useState(ratingA);
  const [rb, setRb] = useState(ratingB);
  const [kf, setKf] = useState(k);
  const [outcome, setOutcome] = useState("a");
  const expectedA = 1 / (1 + Math.pow(10, (rb - ra) / 400));
  const expectedB = 1 - expectedA;
  const scoreA = outcome === "a" ? 1 : outcome === "tie" ? 0.5 : 0;
  const scoreB = 1 - scoreA;
  const deltaA = Math.round(kf * (scoreA - expectedA));
  const deltaB = Math.round(kf * (scoreB - expectedB));
  const fmt = d => d > 0 ? `+${d}` : `${d}`;
  const cls = d => d > 0 ? "dm-up" : d < 0 ? "dm-down" : "dm-muted";
  const clamp = v => Math.max(400, Math.min(2400, Number(v) || 0));
  const outcomes = [["a", "A wins"], ["tie", "Tie"], ["b", "B wins"]];
  return <div className="dm-widget not-prose" role="group" aria-label="ELO calculator">
      <div className="dm-widget-head">
        <div>
          <span className="dm-label">Try it</span>
          <p className="dm-widget-title">ELO calculator</p>
        </div>
        <div style={{
    display: "flex",
    gap: "0.4rem"
  }} role="radiogroup" aria-label="Vote outcome">
          {outcomes.map(([id, text]) => <button key={id} type="button" className="dm-ctl" role="radio" aria-checked={outcome === id} onClick={() => setOutcome(id)}>
              {text}
            </button>)}
        </div>
      </div>

      <div className="dm-grid-2">
        {[["A", ra, setRa, expectedA, deltaA], ["B", rb, setRb, expectedB, deltaB]].map(([name, rating, set, expected, delta]) => <div key={name} className="dm-stat">
            <label className="dm-label" htmlFor={`dm-elo-${name}`}>
              Model {name} rating
            </label>
            <input id={`dm-elo-${name}`} type="number" min={400} max={2400} step={5} value={rating} onChange={e => set(clamp(e.target.value))} className="dm-ctl" style={{
    display: "block",
    width: "100%",
    marginTop: "0.4rem",
    cursor: "text"
  }} />
            <div aria-live="polite">
              <span className="dm-stat-value">
                {rating + delta} <span className={cls(delta)} style={{
    fontSize: "1rem"
  }}>({fmt(delta)})</span>
              </span>
              <span className="dm-muted" style={{
    fontSize: "0.8rem"
  }}>
                Expected score {(expected * 100).toFixed(1)}%
              </span>
            </div>
          </div>)}
      </div>

      <div style={{
    display: "flex",
    flexWrap: "wrap",
    alignItems: "center",
    gap: "0.6rem",
    marginTop: "1rem"
  }}>
        <label className="dm-label" htmlFor="dm-elo-k">
          K-factor
        </label>
        <select id="dm-elo-k" className="dm-ctl" value={kf} onChange={e => setKf(Number(e.target.value))}>
          {[16, 24, 32, 40].map(v => <option key={v} value={v}>
              {v}
            </option>)}
        </select>
        <span className="dm-muted" style={{
    fontSize: "0.8rem"
  }}>
          Higher K moves ratings faster. An upset (the lower-rated model winning) moves the most points.
        </span>
      </div>
    </div>;
};

The [leaderboard](https://arena.dualmindlab.tech/leaderboard/) ranks every model by its **ELO score**, the rating system chess uses. It is built only from Arena votes.

## How ELO works here

<Steps>
  <Step title="Everyone starts level">
    A new model starts at **1000**.
  </Step>

  <Step title="Wins move points">
    When a model wins a vote it gains points and its opponent loses points. Beating a higher-rated model is worth more than beating a lower-rated one.
  </Step>

  <Step title="Upsets matter most">
    Losing to a much lower-rated model costs more than losing to a strong one. Over many battles, scores settle where the community's preferences put them.
  </Step>
</Steps>

<Info>
  ELO adjusts for **who** a model beat, which a raw win rate can't. A model that only ever met weak opponents can have a high win rate and a modest ELO.
</Info>

<Accordion title="The math" icon="sigma">
  For models with ratings R<sub>A</sub> and R<sub>B</sub>, the expected score of A is

  E<sub>A</sub> = 1 / (1 + 10<sup>(R<sub>B</sub> − R<sub>A</sub>) / 400</sup>)

  After a vote, A's new rating is R<sub>A</sub> + K × (S<sub>A</sub> − E<sub>A</sub>), where S<sub>A</sub> is 1 for a win, 0 for a loss and 0.5 for a tie, and K sets how fast ratings move. B is updated the same way, so a win against a stronger opponent (low E<sub>A</sub>) earns more points.
</Accordion>

<EloCalculator />

## Reading the board

<ResponseField name="Rank" type="eloRank">
  Position by ELO, 1 is best. Models with exactly the same score share a rank.
</ResponseField>

<ResponseField name="ELO" type="eloScore">
  The current rating. Differences matter more than absolute values.
</ResponseField>

<ResponseField name="Wins / Losses / Ties" type="totalWins · totalLosses · totalTies">
  Vote outcomes involving the model.
</ResponseField>

<ResponseField name="Battles" type="totalResponses">
  How many comparisons the model has appeared in. More battles means a more settled score.
</ResponseField>

<ResponseField name="Win rate" type="winRate">
  Wins as a percentage of battles (0 to 100).
</ResponseField>

## Live leaderboard

Straight from the public API, refreshed when you load the page. Filter by provider or by how many battles a model has played.

<LiveLeaderboard limit={10} />

## Get the data

The leaderboard is public. No account or token is needed:

<CodeGroup>
  ```bash cURL theme={null}
  curl -s https://api.dualmindlab.tech/api/arena/model-stats | jq '.items[:5][] | {eloRank, displayName, eloScore, winRate}'
  ```

  ```javascript JavaScript theme={null}
  const { items } = await (await fetch("https://api.dualmindlab.tech/api/arena/model-stats")).json();
  const top = items.filter(m => m.totalResponses >= 50).slice(0, 10);
  console.table(top.map(({ eloRank, displayName, providerName, eloScore }) => ({ eloRank, displayName, providerName, eloScore })));
  ```

  ```python Python theme={null}
  import requests

  items = requests.get("https://api.dualmindlab.tech/api/arena/model-stats", timeout=30).json()["items"]
  for m in [m for m in items if m["totalResponses"] >= 50][:10]:
      print(f'{m["eloRank"]:>3}  {m["displayName"]:<40} {m["eloScore"]:.0f}')
  ```
</CodeGroup>

See [Get the leaderboard](/api-reference/voting/get-stats) for every field.

## Caveats

<AccordionGroup>
  <Accordion title="Small samples are noisy" icon="chart-scatter">
    A model with a handful of battles can sit high or low by chance. Look at the battle count before drawing conclusions.
  </Accordion>

  <Accordion title="The prompts come from the community" icon="users">
    Rankings reflect what Arena users ask. If most prompts are about code, the board leans toward coding ability.
  </Accordion>

  <Accordion title="One board for all tasks" icon="layers">
    There is a single overall ranking today. Per-category boards are on the [roadmap](/roadmap).
  </Accordion>
</AccordionGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.