> ## 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.

> ## Agent Instructions
> Prices are US dollars per contract that pays $1: 0.46 means 46 cents. Spread is the Yes ask plus the No ask minus $1. Liquidity grades are deep, tradeable and thin; lists hide thin markets unless include_thin=true. Relations are proven from contract terms, never inferred from wording; a lead marked informational is never executable. Every figure carries a graph_run; read /agents/reading-order first, then /concepts/relations and /concepts/liquidity.

# Liquid opportunities

> Scan leads, venue gaps and strategy matches above a volume and spread floor, rank each by edge × volume, then do it in one MCP call.

## What you'll get

One ranked scan of everything Routeur suggests acting on: mispricing leads, price gaps between proven-equivalent markets, and open markets fitting a strategy, all above the same liquidity floors, each ranked by net edge times 24-hour volume so that a small edge in a deep market outranks a wide one nobody can trade. Then the same scan as a single `find_opportunities` tool call over MCP. On 2026-09-17 no lead or gap paid after fees; the strategy matches did, on history.

## Prerequisites

* An API key in `ROUTEUR_API_KEY`
* Python 3 with `requests`

<Steps>
  <Step title="The three lists, with the floors written out">
    `min_volume_usd`, `max_spread_usd` and `include_thin` work on every list that suggests something to act on. The defaults are \$1,000 traded in 24 hours and a round trip of 5¢ or less; pass them explicitly so the three requests agree.

    ```bash theme={null}
    curl "https://api.routeur.app/v1/opportunities?min_volume_usd=1000&max_spread_usd=0.05&limit=200" -H "X-API-Key: $ROUTEUR_API_KEY"
    curl "https://api.routeur.app/v1/insights?min_volume_usd=1000&max_spread_usd=0.05&limit=50" -H "X-API-Key: $ROUTEUR_API_KEY"
    curl "https://api.routeur.app/v1/insights/strategies?min_volume_usd=1000&max_spread_usd=0.05&limit=10" -H "X-API-Key: $ROUTEUR_API_KEY"
    ```

    On 2026-09-17 12:39 UTC, graph run 74: 4 leads (36 with `include_thin=true`), 0 gaps with a positive `net_usd`, and 20 strategies, 18 of them with open matches. Tightening to `min_volume_usd=5000&max_spread_usd=0.02` left one lead.
  </Step>

  <Step title="Scan and rank">
    ```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"]
    FLOORS = {"min_volume_usd": 1000, "max_spread_usd": 0.05}     # the API's defaults, made explicit


    def get(path, **params):
        res = session.get(f"{API}{path}", params={**FLOORS, **params}, timeout=30)
        res.raise_for_status()
        return res.json()


    def usd(x):
        return float(x) if x not in (None, "") else 0.0


    rows = []

    # 1. Leads: two legs that pay $1 in every outcome. Net edge is per pair; volume is the thinner leg's.
    for lead in get("/v1/opportunities", limit=200)["data"]:
        depth = lead.get("depth") or {}
        if depth.get("limited_by") == "informational":
            continue                                            # the venues could settle it differently
        edge = usd(lead["net_edge_usd"])                        # per pair at the top of book, after estimated fees
        volume = min(usd(lead["left"]["volume_24h_usd"]), usd(lead["right"]["volume_24h_usd"]))
        rows.append({"kind": "lead", "edge": edge, "volume": volume, "executable": depth.get("executable", False),
                     "what": f"{lead['relation']} · {lead['left']['title']} ↔ {lead['right']['title']}",
                     "ids": [(lead["left"]["venue"], lead["left"]["id"]), (lead["right"]["venue"], lead["right"]["id"])]})

    # 2. Venue gaps: the same contract priced apart on Kalshi and Polymarket. net_usd already pays both spreads and fees.
    for gap in get("/v1/insights", limit=50)["data"]["spreads"]:
        volume = min(usd(gap["left"]["volume_24h_usd"]), usd(gap["right"]["volume_24h_usd"]))
        rows.append({"kind": "gap", "edge": usd(gap["net_usd"]), "volume": volume, "executable": None,
                     "what": f"{gap['left']['title']} · {gap['left']['venue']} {gap['left']['yes_ask_usd']} vs {gap['right']['venue']} {gap['right']['yes_ask_usd']}",
                     "ids": [(gap["left"]["venue"], gap["left"]["id"]), (gap["right"]["venue"], gap["right"]["id"])]})

    # 3. Strategy matches: open markets fitting a rule settled history paid for. Edge is the rule's return per $1, before fees.
    for rule in get("/v1/insights/strategies", limit=10)["data"]:
        if rule["open_matches"] == 0:
            continue
        for match in get(f"/v1/insights/strategies/{rule['id']}", limit=50)["data"]["matches"]:
            m = match["market"]
            rows.append({"kind": "strategy", "edge": usd(rule["return_per_100_usd"]) / 100, "volume": usd(m["volume_24h_usd"]), "executable": None,
                         "what": f"buy {rule['side']} at {usd(match['price_usd']) * 100:.0f}¢ · {m['title']} · {rule['id']}",
                         "ids": [(m["venue"], m["id"])]})

    # 4. Rank by edge × volume within each kind: a small edge in a deep market beats a wide one nobody can trade.
    #    The kinds are not comparable with each other: a lead's edge is proven, a gap's is net of fees, a strategy's is history.
    print(f"{len(rows)} candidates above the floors (volume ≥ ${FLOORS['min_volume_usd']:,}, round trip ≤ {FLOORS['max_spread_usd'] * 100:.0f}¢)")
    for kind in ("lead", "gap", "strategy"):
        ranked = sorted((r for r in rows if r["kind"] == kind), key=lambda r: r["edge"] * r["volume"], reverse=True)
        print(f"\n{kind} rows: {len(ranked)}")
        for r in ranked[:3]:
            flag = "" if r["executable"] is None else (" · executable" if r["executable"] else " · not fillable")
            print(f"  {r['edge'] * r['volume']:>+9.0f}  edge {r['edge'] * 100:+.1f}¢ × ${r['volume']:,.0f}{flag}")
            print(f"             {r['what']}")
    ```

    Output observed 2026-09-17 12:52 UTC:

    ```text theme={null}
    146 candidates above the floors (volume ≥ $1,000, round trip ≤ 5¢)

    lead rows: 3
            -42  edge -2.2¢ × $1,874 · not fillable
                 equivalent · Over 8.5 runs scored ↔ Milwaukee Brewers vs. Pittsburgh Pirates: O/U 8.5
           -105  edge -3.0¢ × $3,490 · not fillable
                 contradicts · Who will win the 2026 Texas Railroad Commissioner Election? ↔ Who will win the 2026 Texas Railroad Commissioner Election?
           -389  edge -1.2¢ × $33,216 · not fillable
                 contradicts · Will Karen Bass win the 2026 Los Angeles mayoral election? ↔ Will Nithya Raman win the 2026 Los Angeles mayoral election?

    gap rows: 0

    strategy rows: 143
        +134161  edge +67.8¢ × $197,820
                 buy no at 18¢ · Barcelona wins · all~sports~7d~b8~no
        +126526  edge +64.0¢ × $197,820
                 buy no at 18¢ · Barcelona wins · all~all~7d~b8~no
         +80367  edge +50.2¢ × $160,190
                 buy yes at 11¢ · Will the price of Bitcoin be above $78,000 on September 17? · all~all~1d~b1~yes
    ```

    Every lead's edge is negative after estimated fees and none is fillable, so the honest answer for leads and gaps on this run is "nothing". The strategy rows rank open markets by what their band returned in settled history times how much they trade today; that is a list to research, not a guarantee.
  </Step>

  <Step title="The same scan in one MCP tool call">
    `find_opportunities` reads the same three endpoints, applies `min_volume_usd`, keeps only leads whose books could be filled (`executable_only`, true by default), and returns compact rows with the ids to drill into. It costs up to four API requests against the key.

    ```bash theme={null}
    curl -s https://api.routeur.app/mcp \
      -H "Authorization: Bearer $ROUTEUR_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json, text/event-stream" \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
            "name":"find_opportunities",
            "arguments":{"min_volume_usd":1000,"limit":5}}}'
    ```

    The `structuredContent` of the result, observed 2026-09-17 12:41 UTC:

    ```json theme={null}
    {
      "filters": { "category": "", "cross_venue_only": false, "executable_only": true,
                   "include": ["gaps", "leads", "strategies"], "min_volume_usd": 1000, "venue": "" },
      "graph_run": 74,
      "items": [
        { "kind": "strategy_match",
          "headline": "Will \"BbY WOW - KAROL G, Judeline, rusowsky\" be the #2 US song this w… at $0.15 fits all~culture~1d~b8~no",
          "markets": [ { "venue": "polymarket", "id": "will-bby-wow-karol-g-judeline-rusowsky-be-the-2-us-song-this-week-20260918",
                         "title": "Will \"BbY WOW - KAROL G, Judeline, rusowsky\" be the #2 US song this week?" } ],
          "strategy_id": "all~culture~1d~b8~no",
          "rule": "buy no between $0.80 and $0.90 about 1 days out (all, Culture): $100 came back as $143.03 across 119 settled markets, win rate 0.3697",
          "price_usd": "0.15", "closes_at": "2026-09-18T23:59:00Z", "volume_24h_usd": "4804.56" }
      ],
      "next_cursor": "", "notes": null, "total": 1
    }
    ```

    With `"executable_only": false` the three unfillable leads come back too, each headlined "0 pairs fillable for \$0 net after fees". The tool reads only the best-ranked strategy's matches, so its strategy rows are a subset of the script's.
  </Step>
</Steps>

## Read the result

| Field                                              | Meaning                                                                                                                                                                              |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `net_edge_usd` (lead)                              | What a pair of the two legs pays beyond its cost, at the top of book, after estimated fees. Negative means the legs cost more than the \$1 they guarantee.                           |
| `depth.executable`, `depth.net_usd`, `depth.pairs` | The lead sized against live books at the graph run: whether at least one whole pair could be bought for less than it pays, and how many.                                             |
| `depth.limited_by`                                 | What stopped the fill growing. `informational` means the pair would fill but the venues could settle it differently, so it is never offered as executable, and this recipe skips it. |
| `net_usd` (gap)                                    | What a contract of the gap is worth once both spreads and the estimated taker fees are paid. Gaps are left out unless it is positive, so the list is empty on most runs.             |
| `return_per_100_usd` (strategy)                    | What \$100 across every settled market in the band came back as, before fees. History, not a forecast.                                                                               |
| `volume_24h_usd`                                   | The money behind the row. For a pair, the thinner leg's. The ranking multiplies the edge by this.                                                                                    |
| `liquidity`                                        | `deep`, `tradeable` or `thin`. Everything above the floors is at least `tradeable`.                                                                                                  |

## Pitfalls

* **The three kinds do not share a scale.** A lead's edge is guaranteed by a proof; a gap's is net of fees but not sized; a strategy's is a historical average before fees. Rank within a kind, never across.
* **`executable=true` returned nothing on 2026-09-17.** That is the usual state: leads are mispricings, and mispricings on liquid markets close fast. With `include_thin=true` one lead was `executable` for 1¢ net on a tennis match with \$113 of volume, which is exactly why thin markets are left out by default.
* **`open_matches` and the matches you get can differ** if the two requests use different floors. Pass the same `min_volume_usd` and `max_spread_usd` everywhere, as the script does.
* **Volume is the venue's own figure where it reports one.** Kalshi counts contracts at \$1 face value, Polymarket USDC notional; `volume_source` says which, and `trades` means it was summed from recorded fills instead.
* **The strategy loop can cost up to 12 requests** (one list, up to ten details, plus the leads and insights). At the free tier's 30 per minute, run it once, not in a loop.

## Related

* [Proof trail](/cookbook/proof-trail): read the proof behind a lead and decide from `depth`.
* [Compare venues](/cookbook/compare-venues): every proven-equivalent pair ranked by gap, with and without the floors.
* [Strategy matches](/cookbook/strategy-matches): one rule's open markets with the size behind each.
* [Agent session](/cookbook/agent-session): `find_opportunities`, `explain_relation` and `compare_venues` from an agent.
* [Leads](/concepts/leads): how depth and fees are estimated.
