---
title: "Find Kalshi markets by title or keywords"
method: GET
path: "/api/v1/public/markets/search"
tags: ["Market search (key optional)"]
---

# Find Kalshi markets by title or keywords

`GET /api/v1/public/markets/search`

## Request

- Base URL: `https://kalshispy.com`
- URL: `https://kalshispy.com/api/v1/public/markets/search`
- Auth: one of:
  - HTTP bearer
  - none

Find Kalshi markets by title or keywords, the way the KalshiSpy app's search works: fuzzy title matching finds the market series (so a team, a nickname or an approximate title works), and keyword matching runs against market titles. WITHOUT an API key it returns SETTLED WINS ONLY: settled markets where KalshiSpy's roster of ID-verified, high-profit Kalshi traders held the winning side (wins only, never a hit rate). WITH an Elite or Lifetime API key the same call also returns OPEN markets in `open_markets`: roster positions from the live feed and named traders' recent trades on still-open markets, and it costs 1 unit of the daily quota. Always read `data.access`: it says which answer you got and, when a key was refused, why; relay that sentence rather than guessing. Call this first whenever the user names a market in words, send the words under the argument name this tool's own schema gives, and cite each market's kalshi_url. An empty list is never evidence that Kalshi has no such market. For more wins, follow `settled_wins.more`, which carries ready-made search_settled_wins arguments.

## Query parameters

- `q` string, required
- `limit` integer

## Response `200`

OK

- object
  - `ok` true, required
  - `data` MarketSearch, required
    - `query` string
    - `access` SearchAccess: Which answer this is. Relay `message` when `key` is "rejected": it names the actual problem (revoked, wrong plan, quota, malformed).
      - `tier` 'free' | 'paid'
      - `key` 'none' | 'accepted' | 'rejected'
      - `open_markets_included` boolean
      - `code` string, nullable: The refusal code when key is "rejected", e.g. revoked_api_key, plan_required, quota_exceeded.
      - `message` string
      - `upgrade_url` string, uri, nullable
      - `quota` object, nullable
        - `plan` string
        - `limit` integer
        - `used` integer
        - `remaining` integer
        - `resets_at` string, date-time
    - `resolution` SearchResolution: How the words were matched. series_catalogue: fuzzy-matched to market series as well as by keyword. keywords: keyword matching only, and `reason` says why.
      - `method` 'series_catalogue' | 'keywords'
      - `reason` 'enough_keyword_matches' | 'public_search_budget' | 'catalogue_busy' | 'catalogue_unavailable' | 'null', nullable
      - `cached` boolean
      - `series` object[]: Matched series. Free answers name the tickers only.
        - `series_ticker` string
        - `title` string
        - `category` string
        - `kalshi_url` string, uri
    - `settled_wins` object
      - `status` 'ok' | 'unavailable'
      - `scope` string
      - `window` Window
        - `from` string, date-time
        - `to` string, date-time
        - `weeks_loaded` integer
      - `total_matches` integer
      - `keyword_matches` integer
      - `returned` integer
      - `items` MatchedSettledWin[]
        - `ticker` string
        - `title` string
        - `outcome` string: Kalshi's short outcome label; empty for plain Yes/No markets.
        - `category` string
        - `category_key` 'politics' | 'elections' | 'sports' | 'entertainment' | 'economics' | 'financials' | 'crypto' | 'weather' | 'world' | 'scitech' | 'companies' | 'health' | 'social' | 'transportation' | 'mentions' | 'exotics' | 'other'
        - `series_ticker` string
        - `event_ticker` string
        - `result` 'yes' | 'no'
        - `settled_at` string, date-time
        - `whale_count` integer
        - `dissent_count` integer
        - `consensus_side` 'YES' | 'NO'
        - `consensus_pct` number
        - `net_profit_usd` number
        - `winners_profit_usd` number
        - `whales` SettledWhale[]
          - `nickname` string
          - `side` 'YES' | 'NO'
          - `profit_usd` number
        - `url` string, uri
        - `kalshi_url` string, uri
        - `match` 'keyword' | 'series': keyword: the words are in the title; series: a matched series (listed after keyword matches).
      - `more` object[]: Follow-up archive searches over the same window, as a URL and as search_settled_wins arguments.
        - `label` string
        - `url` string, uri
        - `mcp` object
          - `tool` string
          - `arguments` object
    - `open_markets` object, nullable: null on the free answer. With an Elite or Lifetime key: open markets matching the query.
      - `status` 'ok' | 'partial' | 'unavailable'
      - `scope` string
      - `roster` MatchedLiveMarket[]
        - `ticker` string
        - `event_ticker` string
        - `series_ticker` string
        - `question` string
        - `outcome` string
        - `category` string
        - `category_key` 'politics' | 'elections' | 'sports' | 'entertainment' | 'economics' | 'financials' | 'crypto' | 'weather' | 'world' | 'scitech' | 'companies' | 'health' | 'social' | 'transportation' | 'mentions' | 'exotics' | 'other'
        - `status` 'open' | 'settled'
        - `closes_at` string, date-time, nullable
        - `closes_in` string: Compact label such as "5h" or "2d".
        - `yes_price_cents` integer: The price to buy YES now (the ask), rounded up to whole cents within 1–99; 0 while nobody offers it or the quote is unavailable. Same for NO.
        - `no_price_cents` integer
        - `consensus_side` 'YES' | 'NO'
        - `consensus_pct` number
        - `whale_count` integer
        - `yes_count` integer
        - `no_count` integer
        - `whale_flow_usd` number, nullable
        - `traders_lifetime_pnl_usd` number
        - `realized_profit_usd` number, nullable
        - `whales` WhalePosition[]
          - `nickname` string
          - `side` 'YES' | 'NO'
          - `position_value_usd` number
          - `market_pnl_usd` number
          - `cost_usd` number
          - `lifetime_pnl_usd` number: Lifetime realized P&L in USD. 0 when it is not positive or not available.
          - `roi_pct` number, nullable: Lifetime realized P&L ÷ dollars traded, in percent (40.1 means +40.1%), matching how kalshi.com reports profit and volume. null when no dollar volume is available.
          - `markets_traded` integer
          - `top_category` string, nullable
          - `placed_at` string, date-time, nullable
        - `urls` object
          - `kalshi` string, uri
          - `kalshispy` string, uri
        - `match` 'keyword' | 'series'
      - `recent_trades` RecentlyTradedMarket[]
        - `ticker` string
        - `series_ticker` string
        - `question` string
        - `category` string
        - `category_key` 'politics' | 'elections' | 'sports' | 'entertainment' | 'economics' | 'financials' | 'crypto' | 'weather' | 'world' | 'scitech' | 'companies' | 'health' | 'social' | 'transportation' | 'mentions' | 'exotics' | 'other'
        - `kalshi_url` string, uri
        - `notional_usd` number
        - `traders` object[]
          - `nickname` string
          - `side` 'YES' | 'NO'
          - `contracts` integer
          - `notional_usd` number
          - `avg_price_cents` integer
          - `mark_pnl_usd` number: Mark-to-market P&L at the current price (the bid/ask midpoint; the last trade when the spread is wider than 10¢ or a side has no quotes), summed over these trades.
          - `lifetime_pnl_usd` number
          - `trades` integer
          - `last_trade_ago` string
      - `notes` string[]
  - `meta` Meta, required
    - `generated_at` string, date-time
    - `definition` string
    - `verification` string
    - `docs` string, uri
    - `attribution` string
  - `hints` Hints, required
    - `tip` string
    - `next_steps` object[]
      - `label` string
      - `url` string, uri
      - `auth` 'api_key'
    - `similar` SettledWin[]
      - `ticker` string
      - `title` string
      - `outcome` string: Kalshi's short outcome label; empty for plain Yes/No markets.
      - `category` string
      - `category_key` 'politics' | 'elections' | 'sports' | 'entertainment' | 'economics' | 'financials' | 'crypto' | 'weather' | 'world' | 'scitech' | 'companies' | 'health' | 'social' | 'transportation' | 'mentions' | 'exotics' | 'other'
      - `series_ticker` string
      - `event_ticker` string
      - `result` 'yes' | 'no'
      - `settled_at` string, date-time
      - `whale_count` integer
      - `dissent_count` integer
      - `consensus_side` 'YES' | 'NO'
      - `consensus_pct` number
      - `net_profit_usd` number
      - `winners_profit_usd` number
      - `whales` SettledWhale[]
        - `nickname` string
        - `side` 'YES' | 'NO'
        - `profit_usd` number
      - `url` string, uri
      - `kalshi_url` string, uri

## Other responses

- `400`: Invalid parameter (`error.param` names it) or invalid cursor.
- `429`: Per-IP rate limit (60 requests/minute) on answers without a key; honour `Retry-After`. A key over its daily quota is NOT an error here: the free answer comes back with `data.access` saying so.
- `503`: Search is temporarily unavailable; retry shortly.

---

[API](https://skmtc.dev/kalshispy/apis/kalshispy-api.md) · [All operations](https://skmtc.dev/kalshispy/apis/kalshispy-api/llms.txt) · [OpenAPI document](https://skmtc.dev/kalshispy/apis/kalshispy-api/revisions/5f483277dadc?raw)
