← Back to blog

How to Build a Prediction Market Research Bot with PrismCrawl and the Polymarket or Kalshi API

A prediction-market price tells you what traders collectively believe. It does not tell you whether a new government release was indexed five minutes ago, an official statement changed, or several independent sources just began reporting the same development.

That gap is where a web-research bot becomes useful.

In this guide, we will combine live Google or Bing results from PrismCrawl with market data from Polymarket or Kalshi. The finished program will collect fresh public-web evidence, compare it with a market's current implied probability, and produce an explainable alert for review.

This is a research architecture, not a promise of profitable trading. Search rankings are not probabilities, snippets are not complete evidence, and an apparent price difference is not automatically an edge.

What each API does

The cleanest design gives each service one job:

Polymarket or Kalshi API ──→ market question, rules, price, liquidity
                                  ↓
PrismCrawl API ───────────→ fresh Google/Bing results for that question
                                  ↓
Your program ─────────────→ verify, score, compare, log, and alert
                                  ↓
Optional exchange API ────→ order submission after explicit risk checks

Polymarket exposes public methods for markets, prices, and order books through its CLOB API, as well as authenticated trading methods. Kalshi's exchange API similarly provides market data, order books, and authenticated order entry. PrismCrawl does not replace either exchange API and does not place trades. It gives your program a structured view of what search engines are surfacing outside the market.

Read the current Polymarket CLOB documentation or Kalshi API documentation before building an exchange adapter. Those APIs can evolve independently of your research layer.

Step 1: Define the market before searching

Do not use the short market title as your entire research prompt. Save at least:

  • the exact question;
  • the close and resolution dates;
  • the resolution criteria;
  • the named resolution source, if one exists;
  • relevant people, organizations, locations, and aliases;
  • the outcome token ID for Polymarket or market ticker for Kalshi.

Resolution language matters. "Will an agency announce a policy?" is different from "Will the policy take effect?" A bot that ignores that distinction can find factually correct pages that are irrelevant to the contract.

From that record, generate a small query set rather than one broad query:

def research_queries(question: str, primary_domain: str | None = None) -> list[str]:
    queries = [
        f'"{question}"',
        f'{question} latest announcement',
        f'{question} official statement',
    ]
    if primary_domain:
        queries.insert(0, f'site:{primary_domain} {question}')
    return queries

The primary-source query is particularly valuable. If a contract resolves from an agency, league, court, or company publication, that source deserves more weight than commentary about it.

Step 2: Add PrismCrawl as the evidence-discovery layer

Create a PrismCrawl API key and keep it in a server-side environment variable. The wrapper below asks Google for results from the last day and returns only the fields the research loop needs:

import os
import requests

PRISMCRAWL_API_KEY = os.environ["PRISMCRAWL_API_KEY"]
PRISMCRAWL_BASE_URL = os.environ["PRISMCRAWL_BASE_URL"]

def search_recent_evidence(query: str) -> list[dict]:
    response = requests.post(
        f"{PRISMCRAWL_BASE_URL}/v1/google/search",
        headers={"x-api-key": PRISMCRAWL_API_KEY},
        json={
            "query": query,
            "html": False,
            "gl": "us",
            "hl": "en-US",
            "tbs": "qdr:d",
        },
        timeout=60,
    )
    response.raise_for_status()
    body = response.json()

    if not body["success"]:
        raise RuntimeError(body["error"]["message"])

    return [
        {
            "request_id": body["request_id"],
            "rank": item["rank"],
            "title": item["title"],
            "url": item["url"],
            "domain": item["domain"],
            "snippet": item["snippet"],
            "source": item["source_name"],
        }
        for item in body["data"]["content"]["results"]
        if item["type"] == "organic"
    ]

The tbs value can be widened to qdr:w or a custom date range when a daily window is too narrow. You can also call /v1/microsoft/search to compare Bing coverage. See the API reference for the complete request and response schemas.

Using two search engines can improve discovery, but two results linking to the same article are still one source. Deduplicate by canonical URL and, where possible, by the underlying report being cited.

Step 3: Read the market price from the exchange

Keep the exchange integration behind a tiny adapter. The rest of the application should not care where the price came from:

from dataclasses import dataclass

@dataclass
class MarketSnapshot:
    venue: str
    market_id: str
    question: str
    yes_bid: float
    yes_ask: float

    @property
    def midpoint(self) -> float:
        return (self.yes_bid + self.yes_ask) / 2

For Polymarket, resolve the market's outcome token and read its order book or price through the CLOB API. For Kalshi, read the market or order book by ticker. Kalshi's public market response includes dollar-denominated YES bid and ask fields; its order book represents bids on both sides. The official references document the exact current shapes:

Use executable bid and ask prices in real decisions, not only the last trade. A displayed midpoint can hide a wide spread or shallow liquidity. Fees, slippage, order size, and venue mechanics also affect whether an apparent difference is actionable.

Step 4: Normalize and score evidence

The tempting shortcut is to ask a language model, "What is the probability?" That produces a precise-looking number without a reproducible chain of evidence. A better first version scores observable properties and retains every input.

from urllib.parse import urlparse

PRIMARY_DOMAINS = {"example.gov", "official-source.example"}

def source_weight(item: dict) -> float:
    domain = urlparse(item["url"]).netloc.removeprefix("www.")
    if domain in PRIMARY_DOMAINS:
        return 1.0
    if item["rank"] <= 3:
        return 0.5
    return 0.25

def unique_evidence(items: list[dict]) -> list[dict]:
    seen = set()
    output = []
    for item in items:
        normalized = item["url"].split("#", 1)[0].rstrip("/")
        if normalized not in seen:
            seen.add(normalized)
            output.append(item)
    return output

Ranking can help prioritize what to inspect, but it is not a truth score. The next production step should fetch the selected pages, verify publication timestamps and quotations, and classify whether each source supports, contradicts, or merely mentions the market claim.

If you use an LLM for that classification, require structured output and include an unclear option. Store the model version, prompt, source URL, excerpt, and classification so the result can be audited later.

Step 5: Compare evidence with the market

Your alert rule should be understandable without reading model internals. For example:

def build_alert(snapshot: MarketSnapshot, estimated_probability: float, evidence: list[dict]):
    difference = estimated_probability - snapshot.midpoint
    independent_domains = {item["domain"] for item in evidence}

    if abs(difference) < 0.10 or len(independent_domains) < 2:
        return None

    return {
        "market": snapshot.question,
        "venue": snapshot.venue,
        "market_midpoint": round(snapshot.midpoint, 4),
        "research_estimate": round(estimated_probability, 4),
        "difference": round(difference, 4),
        "source_count": len(independent_domains),
        "sources": [item["url"] for item in evidence[:5]],
        "action": "review",
    }

The ten-percentage-point threshold is illustrative, not a recommended strategy. A useful alert should also include the market rules, bid/ask spread, evidence timestamps, PrismCrawl request IDs, and the reasons behind the estimate. "Review these three new primary sources" is much safer and more useful than "buy YES."

Step 6: Run it as a change detector

Repeatedly collecting the same top ten links creates noise. Save a fingerprint of every result set and alert on changes:

  • a new primary-source URL appears;
  • a tracked source changes its title or snippet;
  • several independent domains publish within a short window;
  • the exchange price moves without corresponding search evidence;
  • strong new evidence appears before a meaningful price move.

Schedule searches according to the event. A market resolving next month may need hourly or daily checks; a fast-moving event may need a specialized direct feed rather than web search. PrismCrawl is best used for discovery and monitoring, not millisecond execution.

Before enabling order submission

Both exchanges provide authenticated order APIs. Polymarket documents CLOB order creation, and Kalshi documents signed order requests. That does not mean the research loop should immediately call them.

Start in this order:

  1. Log signals without taking action.
  2. Backtest against stored market snapshots and contemporaneous search results.
  3. Paper trade with realistic spreads, fees, latency, and rejected orders.
  4. Send human-reviewed alerts.
  5. Only then consider tightly limited automatic execution.

If you add execution, isolate it in a separate service with a maximum order size, maximum daily exposure, stale-data cutoff, duplicate-order protection, and a kill switch. Never let an LLM invent order parameters or bypass deterministic limits. Confirm your eligibility and comply with the venue's current rules, API agreement, and jurisdictional requirements.

See the official Polymarket order documentation and Kalshi order documentation for current authentication and payload requirements.

Where this architecture works best

The approach is well suited to markets affected by public announcements and documents: regulatory decisions, company statements, court updates, election reporting, legislation, and scheduled institutional releases.

It is less suitable when the decisive information comes from a faster specialized feed. Sports scores, weather observations, and second-by-second financial prices usually have authoritative APIs that should be queried directly. PrismCrawl can still discover context, but it should not replace the primary feed.

The durable pattern is market data plus external evidence, with a clear boundary between research and execution. Polymarket or Kalshi tells your program what the market believes. PrismCrawl helps it discover what the public web is saying now—and preserves the sources needed to explain why an alert fired.

Frequently asked questions

Can PrismCrawl place trades on Polymarket or Kalshi?

No. PrismCrawl supplies live Google and Bing search results for the research layer. Your application reads market data and, if you deliberately enable execution, submits orders through the official exchange API.

Are search-result snippets enough to make an automated trade?

Usually not. Search results are discovery signals, not proof. Fetch and verify important underlying pages, prioritize primary sources, check the market's exact resolution rules, and apply deterministic risk controls before taking action.

Should I start with automatic prediction-market trading?

Start with logs, backtesting, paper trading, and human-reviewed alerts. Automatic execution should come only after the signal, data quality, failure handling, position limits, and platform requirements have been tested independently.