Arbitrage involves risk - odds change rapidly, no guaranteed profits.

How to Programmatically Identify Arbitrage Opportunities on Polymarket

How to Programmatically Identify Arbitrage Opportunities on Polymarket

Find Polymarket arbitrage instantly with the ArbBets API. We scan Polymarket vs Kalshi and Polymarket vs Opinion every second — you get structured arbitrage data with a single API call.

TL;DR

The ArbBets API lets you programmatically detect Polymarket arbitrage with a single HTTP request. Two endpoints do all the heavy lifting: kalshi-polymarket-full finds Kalshi vs Polymarket arbs, and opinion-polymarket-full finds Opinion vs Polymarket arbs. Both return structured JSON with prices, ROI, and orderbook data — no need to connect to multiple platform APIs yourself. This guide covers authentication, both endpoints, response parsing, and building an automated scanner with working Python code.

Why Use the ArbBets API?

Building a cross-platform arbitrage detector from scratch means connecting to the Polymarket CLOB API, Kalshi's REST API, and Opinion's API separately, normalizing market names across platforms, matching equivalent contracts, and calculating spreads in real-time. That's months of engineering.

The ArbBets API does all of this server-side. You send one request, you get back every Polymarket arbitrage opportunity with prices and ROI already calculated.

ApproachSetup TimeMaintenanceCoverage
Build from scratchWeeks-monthsConstant (API changes, market matching)Whatever you build
ArbBets APIMinutesZero (we handle it)Polymarket + Kalshi + Opinion

According to ArbBets data, the API surfaces 100+ prediction market arbitrage opportunities daily with an average ROI of 4.87%, scanning across Polymarket, Kalshi, and Opinion continuously.

— Source: ArbBets real-time market data

Step 1: Get Your API Key

  1. Sign up at getarbitragebets.com
  2. Subscribe to a Premium or Enterprise plan
  3. Go to Dashboard → API to generate your API key
  4. Your key starts with ak_ — example: ak_3f8a1b2c4d5e6f...

You get 5,000 API credits per month on Premium. Both Polymarket endpoints cost just 1 credit per call — that's 5,000 requests per month.

Step 2: The Two Polymarket Endpoints

EndpointWhat It DoesCredits
/api/internal/kalshi-polymarket-fullFinds arbitrage between Kalshi and Polymarket with full orderbook data1
/api/internal/opinion-polymarket-fullFinds arbitrage between Opinion and Polymarket with full orderbook data1

Both endpoints accept the same query parameters:

ParameterTypeDescriptionDefault
investmentnumberYour investment amount in dollars100
min_profitnumberMinimum profit percentage to return1.0

Authentication is via the Authorization: Bearer {apiKey} header on every request.

Step 3: Kalshi vs Polymarket Arbitrage

This is the primary endpoint for Polymarket arbitrage. It compares orderbooks across Kalshi and Polymarket and returns every opportunity where the combined cost of covering both sides is less than $1.00.

Python

import requests
 
API_KEY = "ak_your_api_key_here"
 
headers = {"Authorization": f"Bearer {API_KEY}"}
params = {"investment": 100, "min_profit": 1.0}
 
response = requests.get(
    "https://getarbitragebets.com/api/internal/kalshi-polymarket-full",
    headers=headers,
    params=params,
)
 
data = response.json()
 
print(f"Found {len(data)} Kalshi vs Polymarket opportunities\n")
for opp in data:
    print(f"Market: {opp.get('market_name', 'N/A')}")
    print(f"  ROI: {opp.get('roi', 'N/A')}%")
    print(f"  Polymarket price: {opp.get('polymarket_price', 'N/A')}")
    print(f"  Kalshi price: {opp.get('kalshi_price', 'N/A')}")
    print()

JavaScript

const API_KEY = "ak_your_api_key_here";
 
const params = new URLSearchParams({
  investment: "100",
  min_profit: "1.0",
});
 
fetch(
  `https://getarbitragebets.com/api/internal/kalshi-polymarket-full?${params}`,
  { headers: { Authorization: `Bearer ${API_KEY}` } }
)
  .then((res) => res.json())
  .then((data) => {
    console.log(`Found ${data.length} Kalshi vs Polymarket opportunities`);
    data.forEach((opp) => {
      console.log(`${opp.market_name}: ${opp.roi}% ROI`);
    });
  });

Node.js

const axios = require("axios");
 
const API_KEY = "ak_your_api_key_here";
 
const response = await axios.get(
  "https://getarbitragebets.com/api/internal/kalshi-polymarket-full",
  {
    headers: { Authorization: `Bearer ${API_KEY}` },
    params: { investment: 100, min_profit: 1.0 },
  }
);
 
console.log(`Found ${response.data.length} opportunities`);
response.data.forEach((opp) => {
  console.log(`${opp.market_name}: ${opp.roi}% ROI`);
});

cURL

curl -H "Authorization: Bearer ak_your_api_key_here" \
  "https://getarbitragebets.com/api/internal/kalshi-polymarket-full?investment=100&min_profit=1.0"

Step 4: Opinion vs Polymarket Arbitrage

The second endpoint covers a different counterparty — Opinion. The API call is identical except for the path:

Python

import requests
 
API_KEY = "ak_your_api_key_here"
 
headers = {"Authorization": f"Bearer {API_KEY}"}
params = {"investment": 100, "min_profit": 1.0}
 
response = requests.get(
    "https://getarbitragebets.com/api/internal/opinion-polymarket-full",
    headers=headers,
    params=params,
)
 
data = response.json()
 
print(f"Found {len(data)} Opinion vs Polymarket opportunities\n")
for opp in data:
    print(f"Market: {opp.get('market_name', 'N/A')}")
    print(f"  ROI: {opp.get('roi', 'N/A')}%")
    print()

cURL

curl -H "Authorization: Bearer ak_your_api_key_here" \
  "https://getarbitragebets.com/api/internal/opinion-polymarket-full?investment=100&min_profit=1.0"

Step 5: Combine Both Endpoints for Full Coverage

To catch every Polymarket arbitrage opportunity, call both endpoints and merge the results. This costs 2 credits total:

import requests
from datetime import datetime
 
API_KEY = "ak_your_api_key_here"
BASE = "https://getarbitragebets.com"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
PARAMS = {"investment": 200, "min_profit": 1.0}
 
def fetch(endpoint: str) -> list:
    resp = requests.get(f"{BASE}{endpoint}", headers=HEADERS, params=PARAMS, timeout=30)
    if resp.status_code == 200:
        return resp.json() if isinstance(resp.json(), list) else []
    print(f"  {endpoint} returned {resp.status_code}")
    return []
 
# Fetch from both Polymarket endpoints
kalshi_poly = fetch("/api/internal/kalshi-polymarket-full")
opinion_poly = fetch("/api/internal/opinion-polymarket-full")
 
all_opps = kalshi_poly + opinion_poly
all_opps.sort(key=lambda x: float(x.get("roi", 0)), reverse=True)
 
print(f"=== Polymarket Arbitrage ({datetime.now().strftime('%Y-%m-%d %H:%M')}) ===\n")
print(f"Kalshi vs Polymarket:  {len(kalshi_poly)} opportunities")
print(f"Opinion vs Polymarket: {len(opinion_poly)} opportunities")
print(f"Total:                 {len(all_opps)} opportunities\n")
 
for i, opp in enumerate(all_opps[:10], 1):
    market = opp.get("market_name", "Unknown")[:55]
    roi = opp.get("roi", "?")
    print(f"  {i}. {market}")
    print(f"     ROI: {roi}%")

Step 6: Build a Continuous Scanner

Here's a complete scanner that polls both endpoints on an interval, tracks new opportunities, and can trigger alerts:

import requests
import time
from datetime import datetime
 
API_KEY = "ak_your_api_key_here"
BASE_URL = "https://getarbitragebets.com"
 
SCAN_INTERVAL = 120   # seconds between scans (respect rate limits)
MIN_PROFIT = 2.0      # minimum ROI %
INVESTMENT = 500      # dollar amount per opportunity
 
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
PARAMS = {"investment": INVESTMENT, "min_profit": MIN_PROFIT}
 
ENDPOINTS = [
    "/api/internal/kalshi-polymarket-full",
    "/api/internal/opinion-polymarket-full",
]
 
 
def scan_polymarket_arbs() -> list:
    """Fetch all Polymarket arbitrage from both endpoints."""
    all_opps = []
    for endpoint in ENDPOINTS:
        try:
            response = requests.get(
                f"{BASE_URL}{endpoint}",
                headers=HEADERS,
                params=PARAMS,
                timeout=30,
            )
            if response.status_code == 200:
                data = response.json()
                if isinstance(data, list):
                    all_opps.extend(data)
            elif response.status_code == 401:
                print("[ERROR] Invalid API key")
            elif response.status_code == 402:
                print("[ERROR] Out of credits")
            elif response.status_code == 429:
                print("[WARN] Rate limited — backing off")
                time.sleep(60)
        except requests.exceptions.RequestException as e:
            print(f"[ERROR] {endpoint}: {e}")
    return all_opps
 
 
def main():
    print("Polymarket Arbitrage Scanner (ArbBets API)")
    print(f"Endpoints: kalshi-polymarket-full + opinion-polymarket-full")
    print(f"Min ROI: {MIN_PROFIT}% | Investment: ${INVESTMENT}")
    print(f"Scanning every {SCAN_INTERVAL}s (2 credits per scan)")
    print("-" * 60)
 
    seen = set()
 
    while True:
        ts = datetime.now().strftime("%H:%M:%S")
        opps = scan_polymarket_arbs()
 
        for opp in opps:
            market = opp.get("market_name", "Unknown")
            roi = opp.get("roi", 0)
 
            if market not in seen:
                seen.add(market)
                print(f"\n[{ts}] NEW: {market}")
                print(f"  ROI: {roi}% | ${INVESTMENT} investment")
 
        print(f"[{ts}] {len(opps)} active Polymarket arbs")
        time.sleep(SCAN_INTERVAL)
 
 
if __name__ == "__main__":
    main()

This uses 2 credits per scan cycle (1 per endpoint). At 5,000 credits/month, you can run 2,500 scan cycles — over 80 per day on a 2-minute interval.

Step 7: Add Discord Alerts

Get notified the instant a new Polymarket arbitrage appears:

import requests as req
 
DISCORD_WEBHOOK = "https://discord.com/api/webhooks/your/webhook"
 
def send_discord_alert(opportunity: dict):
    """Send Polymarket arbitrage alert to Discord."""
    market = opportunity.get("market_name", "Unknown")
    roi = opportunity.get("roi", 0)
 
    req.post(DISCORD_WEBHOOK, json={
        "embeds": [{
            "title": f"Polymarket Arb: {roi}% ROI",
            "description": f"**{market}**",
            "color": 0x00FF00,
            "fields": [
                {"name": "ROI", "value": f"{roi}%", "inline": True},
                {"name": "Investment", "value": "$500", "inline": True},
            ],
            "footer": {"text": "ArbBets API • kalshi-polymarket-full + opinion-polymarket-full"},
        }]
    })

Wire it into the scanner by replacing the print in the if market not in seen: block with send_discord_alert(opp).

API Response Codes

CodeMeaningWhat To Do
200Success — JSON array of opportunitiesParse and process
401Invalid or missing API keyCheck Authorization: Bearer ak_... header
402Out of creditsUpgrade plan or wait for monthly reset
429Rate limited (15 requests / 5 minutes)Back off, retry after 60 seconds
503Backend temporarily unavailableRetry in a few minutes

Credit Math

Both Polymarket endpoints cost 1 credit each. Here's what your monthly budget looks like:

Scan FrequencyCredits Per DayDays Per MonthTotal Credits
Every 5 min (both endpoints)5763017,280 (Enterprise)
Every 5 min (one endpoint)288308,640 (Enterprise)
Every 10 min (both endpoints)288308,640 (Enterprise)
Every 2 min (both endpoints)1,4403043,200 (Enterprise)
Every 15 min (both endpoints)192305,760 (Premium)
Every 30 min (both endpoints)96302,880 (Premium)

On Premium (5,000 credits/month), scanning both endpoints every 15 minutes gives you all-day coverage with credits to spare.

Get your API key and start finding Polymarket arbitrage programmatically. Get API access →

Frequently Asked Questions

How much does the ArbBets API cost?

The API is included with Premium ($49/month, 5,000 credits) and Enterprise plans (999,999 credits). Both Polymarket endpoints — kalshi-polymarket-full and opinion-polymarket-full — cost just 1 credit each. On Premium, you can call both endpoints over 2,500 times per month.

What is the rate limit?

15 requests per 5-minute window per IP address. If you exceed this, you get a 429 status code. A 2-minute scan interval stays well within limits. Most Polymarket arbitrage windows last 10+ minutes, so even a 15-minute interval catches the majority of opportunities.

Do I need accounts on Polymarket and Kalshi to use the API?

No. The ArbBets API detects and reports arbitrage opportunities — it does not execute trades. You only need an ArbBets API key to fetch data. To act on the opportunities, you will need funded accounts on Polymarket and/or Kalshi to place the trades yourself.

How fresh is the data?

The ArbBets backend scans prediction market orderbooks continuously in real-time. When you call either endpoint, you get the latest snapshot at that moment. Prices are typically seconds old, not minutes.

Can I use this to build my own trading bot?

Yes. The API is designed for programmatic access. Call kalshi-polymarket-full and opinion-polymarket-full for signal detection, then use Polymarket's CLOB API and Kalshi's REST API directly for order execution. The ArbBets API handles the expensive part — scanning, matching markets across platforms, and calculating spreads — so you can focus purely on execution.