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

# Compare a market across venues

> Rank equivalent Kalshi and Polymarket markets by how far apart they are priced.

Two markets proven `equivalent` should cost the same. This recipe lists the pairs, reads both asks, and ranks them by the gap.

<CodeGroup>
  ```python Python theme={null}
  import os
  import requests

  API = "https://api.routeur.app"
  session = requests.Session()
  session.headers["X-API-Key"] = os.environ["ROUTEUR_API_KEY"]

  def equivalent_pairs(pages=5, limit=200):
      for page in range(pages):
          res = session.get(f"{API}/v1/cross-venue",
                            params={"relation": "equivalent", "limit": limit, "offset": page * limit}, timeout=20)
          res.raise_for_status()
          data = res.json()["data"]
          yield from data
          if len(data) < limit:
              return

  def yes_ask(market):
      ask = (market.get("quote") or {}).get("yes_ask_usd")
      return float(ask) if ask else None

  gaps = []
  for pair in equivalent_pairs():
      left, right = yes_ask(pair["left"]), yes_ask(pair["right"])
      if left is None or right is None:
          continue
      gaps.append((abs(left - right), pair))

  for gap, pair in sorted(gaps, key=lambda g: g[0], reverse=True)[:10]:
      l, r = pair["left"], pair["right"]
      print(f"{gap * 100:4.0f}¢  {l['title']}")
      print(f"       {l['venue']}: {yes_ask(l) * 100:.0f}¢   {r['venue']}: {yes_ask(r) * 100:.0f}¢")
  ```

  ```typescript TypeScript theme={null}
  const API = 'https://api.routeur.app';
  const headers = { 'X-API-Key': process.env.ROUTEUR_API_KEY! };

  type Market = { venue: string; id: string; title: string; quote?: { yes_ask_usd?: string } };
  type Pair = { relation: string; left: Market; right: Market };

  async function equivalentPairs(pages = 5, limit = 200): Promise<Pair[]> {
    const all: Pair[] = [];
    for (let page = 0; page < pages; page++) {
      const params = new URLSearchParams({ relation: 'equivalent', limit: String(limit), offset: String(page * limit) });
      const res = await fetch(`${API}/v1/cross-venue?${params}`, { headers });
      if (!res.ok) throw new Error(`cross-venue: ${res.status}`);
      const { data } = (await res.json()) as { data: Pair[] };
      all.push(...data);
      if (data.length < limit) break;
    }
    return all;
  }

  const yesAsk = (m: Market) => (m.quote?.yes_ask_usd ? Number(m.quote.yes_ask_usd) : undefined);

  const ranked = (await equivalentPairs())
    .map(pair => ({ pair, left: yesAsk(pair.left), right: yesAsk(pair.right) }))
    .filter(p => p.left !== undefined && p.right !== undefined)
    .map(p => ({ ...p, gap: Math.abs(p.left! - p.right!) }))
    .sort((a, b) => b.gap - a.gap)
    .slice(0, 10);

  for (const { pair, left, right, gap } of ranked) {
    console.log(`${Math.round(gap * 100)}¢  ${pair.left.title}`);
    console.log(`     ${pair.left.venue}: ${Math.round(left! * 100)}¢   ${pair.right.venue}: ${Math.round(right! * 100)}¢`);
  }
  ```
</CodeGroup>

## Going further

* **Skip the ranking:** `GET /v1/insights` returns `spreads`, the pairs priced furthest apart right now, already sorted.
* **Check it can be filled:** a gap is not a trade. `GET /v1/opportunities?cross_venue=true&executable=true` returns only leads whose order books could be filled at a profit after fees. See [Leads](/concepts/leads).
* **Get told instead of polling:** subscribe to [`lead.executable`](/webhooks/overview).
