Skip to main content
The arbitrage opportunities endpoint is the core data feed for API key customers. It returns a scored and classified list of price discrepancies detected across Predexy’s integrated platforms — Polymarket, Limitless, Predict.fun, Manifold, PredictIt, Azuro, and Drift — so your bot can evaluate and act on them programmatically. Every opportunity includes fee-adjusted profit estimates, lifecycle status, and a multi-factor quality score to help you separate signal from noise.

Endpoint

GET https://api.predexy.com/api/v1/external/arbitrage/opportunities
Authentication: X-API-Key header with your pdx_ key. This endpoint requires the read:arbitrage permission.

Query parameters

ParameterTypeDefaultDescription
min_scoreinteger (0–100)40Minimum arbitrage score to include.
classificationstringFilter by tier: actionable, informational, or noise.
categorystringMarket category, e.g. crypto, politics.
statusstringLifecycle state: active, stale, or resolved.
guaranteestringPolicy guarantee class: STRICT or QUASI.
risk_gatestringRuntime risk gate: allowed, degraded, or blocked.
limitinteger (1–200)50Maximum number of opportunities to return.

Understanding classification

Predexy’s scoring engine assigns each opportunity a score from 0 to 100 based on three factors:
  • Execution quality — spread magnitude, net profit after fees, fee efficiency
  • Market quality — liquidity depth, 24-hour trading volume
  • Temporal quality — time remaining before market expiry (longer is better)
That score maps to a classification tier:
ClassificationScore rangeAdditional criteria
actionable≥ 70Positive net spread, liquidity > $500
informational40–69Or low liquidity at any score
noise< 40

Understanding risk gate

The risk_gate_state field reflects the current runtime governance state:
  • allowed — no restrictions; normal execution
  • degraded — execution may be affected by platform or global controls
  • blocked — execution is blocked by policy
Combine classification=actionable and risk_gate=allowed to get the best opportunities with no execution restrictions. Add status=active to exclude stale or resolved entries.

Example request

The following fetches up to 25 actionable, active opportunities with a minimum score of 70, filtered to risk gate allowed:
curl "https://api.predexy.com/api/v1/external/arbitrage/opportunities\
?classification=actionable\
&status=active\
&min_score=70\
&risk_gate=allowed\
&limit=25" \
  -H "X-API-Key: pdx_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

Sample response

{
  "data": {
    "opportunities": [
      {
        "id": "arb_poly_limit_abc123",
        "canonical_question_id": "550e8400-e29b-41d4-a716-446655440000",
        "question_title": "Will BTC reach $100k by 2026?",
        "question_slug": "will-btc-reach-100k-by-2026",
        "category": "crypto",
        "type": "direct",
        "buy_platform": {
          "id": "a1b2c3d4-...",
          "slug": "limitless",
          "name": "Limitless"
        },
        "sell_platform": {
          "id": "e5f6g7h8-...",
          "slug": "polymarket",
          "name": "Polymarket"
        },
        "buy_price": 0.42,
        "sell_price": 0.48,
        "spread": 0.06,
        "spread_bps": 600,
        "net_spread": 0.02,
        "estimated_profit": 2.00,
        "arbitrage_score": 78,
        "classification": "actionable",
        "status": "active",
        "liquidity": 15000.00,
        "volume24h": 52000.00,
        "risk_gate_state": "allowed",
        "guarantee": "STRICT",
        "detected_at": "2026-04-26T10:15:00Z",
        "fees": {
          "buy_fee": 0.02,
          "sell_fee": 0.02,
          "total_fees": 0.04
        }
      }
    ],
    "stats": {
      "total_opportunities": 47,
      "actionable_count": 12,
      "informational_count": 23,
      "avg_spread": 0.034,
      "max_profit": 8.50,
      "platform_pairs": {
        "polymarket↔limitless": 15,
        "polymarket↔predictfun": 8
      },
      "scanned_questions": 312,
      "duration_ms": 1250,
      "scanned_at": "2026-04-26T10:15:05Z"
    }
  },
  "meta": {
    "count": 1,
    "offset": 0,
    "limit": 25,
    "source": "database"
  }
}

Reading the response

Opportunity fields

Each object in opportunities represents a detected price discrepancy. Key fields:
FieldDescription
idUnique opportunity identifier
question_titleHuman-readable question text
typedirect (same outcome, different prices) or dutch_book (buy Yes on one platform + No on another for < $1 combined)
buy_platform / sell_platformPlatform objects with id, slug, and name
buy_price / sell_pricePrices as probabilities (0–1)
net_spreadSpread remaining after deducting platform fees
estimated_profitEstimated profit per $100 deployed
arbitrage_scoreQuality score (0–100)
classificationactionable, informational, or noise
statusactive, stale, or resolved
risk_gate_stateallowed, degraded, or blocked
guaranteeSTRICT (policy-approved) or QUASI (weaker guarantee)
feesBreakdown of buy_fee, sell_fee, and total_fees as fractions

Stats object

The stats object summarizes the most recent scan across all opportunities (not just the filtered subset you received):
FieldDescription
total_opportunitiesTotal detected in this scan
actionable_countHow many scored ≥ 70 with positive profit and adequate liquidity
informational_countHow many scored 40–69 or had low liquidity
avg_spreadAverage raw spread across all opportunities
max_profitHighest estimated profit per $100 in this scan
scanned_questionsTotal canonical questions evaluated
platform_pairsPer-pair opportunity counts
Use stats to understand current market conditions before acting on individual opportunities. A low actionable_count or low avg_spread signals a quiet market.

meta.source

The meta.source field tells you where the data came from:
ValueMeaning
databaseLive data served directly from the database
cacheServed from a short-lived cache layer
emptyNo opportunities matched; response is empty

Polling for opportunities

Most trading bots poll this endpoint on a schedule. The background scanner runs continuously, so polling every 30–60 seconds gives you fresh data without burning your rate limit.
import httpx
import time

API_KEY = "pdx_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
BASE_URL = "https://api.predexy.com"
POLL_INTERVAL_SECONDS = 30

def fetch_opportunities():
    response = httpx.get(
        f"{BASE_URL}/api/v1/external/arbitrage/opportunities",
        headers={"X-API-Key": API_KEY},
        params={
            "classification": "actionable",
            "status": "active",
            "min_score": 70,
            "risk_gate": "allowed",
            "limit": 50,
        },
        timeout=10,
    )

    if response.status_code == 429:
        reset = int(response.headers.get("X-RateLimit-Reset", time.time() + 60))
        wait = max(0, reset - time.time())
        print(f"Rate limited. Waiting {wait:.0f}s.")
        time.sleep(wait)
        return []

    response.raise_for_status()
    payload = response.json()
    return payload["data"]["opportunities"]

while True:
    opportunities = fetch_opportunities()
    print(f"Found {len(opportunities)} actionable opportunities")
    for opp in opportunities:
        # your execution logic here
        pass
    time.sleep(POLL_INTERVAL_SECONDS)
Check X-RateLimit-Remaining on each response. If it drops near zero, slow your polling cadence or upgrade your key’s rate limit in the Developer Console.