> ## Documentation Index
> Fetch the complete documentation index at: https://docs.probsights.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Arbitrage history

> Paged historical arbitrage opportunities recorded by the background scanner.

Paged feed of every arbitrage opportunity episode recorded by the always-on
background scanner, **newest first**. Repeated detections of the same opportunity
extend an existing episode instead of creating a duplicate.

This is the endpoint the [Dashboard](https://probsights.com/dashboard) arbitrage
table reads from, and the only place `min_net_cents` is applied server-side across
**all** recorded opportunities (not just the rows currently loaded in the UI).

Returns a page envelope: `{items, total, offset, limit, has_more}`.

Preview access (anonymous visitors, free accounts, unauthenticated API keys)
returns the first opportunity unmasked and locks the rest — see
[Preview masking](#preview-masking).

## Request

```
https://api.probsights.com/v1/arbitrage/history
```

### Query parameters

<ParamField query="limit" type="integer" default="50">
  Max items per page (1–100).
</ParamField>

<ParamField query="offset" type="integer" default="0">
  Zero-based page offset.
</ParamField>

<ParamField query="interval_code" type="string">
  Filter by interval: `5m`, `15m`, `1h`, `24h`, or `1w`. Omit for all.
</ParamField>

<ParamField query="category" type="string">
  Filter by `crypto` or `sports`.
</ParamField>

<ParamField query="min_net_cents" type="number" default="0">
  Minimum net profit after fees, in cents. Applied server-side over the full
  history in the database, independent of the loaded page.
</ParamField>

<ParamField query="from" type="string">
  ISO-8601 timestamp. Keep episodes last seen at or after this.
</ParamField>

<ParamField query="to" type="string">
  ISO-8601 timestamp. Keep episodes first seen at or before this.
</ParamField>

## Response

Fields `opinion_market_id` and `opinion` are `null` for BTC interval scope and
omitted. All monetary fields are `null` for locked (preview) rows.

<ResponseField name="items" type="array">
  List of opportunity episodes, newest first.
</ResponseField>

<ResponseField name="total" type="int">
  Total episodes matching the filters (before paging).
</ResponseField>

<ResponseField name="offset" type="int">
  Zero-based offset used for this page.
</ResponseField>

<ResponseField name="limit" type="int">
  Page size used for this request.
</ResponseField>

<ResponseField name="has_more" type="boolean">
  Whether another page exists after this one.
</ResponseField>

### Episode

<ResponseField name="first_seen_at" type="string">
  First timestamp this opportunity was observed.
</ResponseField>

<ResponseField name="last_seen_at" type="string">
  Most recent timestamp this opportunity was observed.
</ResponseField>

<ResponseField name="opportunity" type="object">
  The detected [opportunity](#opportunity) object.
</ResponseField>

### Opportunity

<ResponseField name="event_id" type="string">
  Canonical event identifier (e.g. `btc-updown-15m-1789273800`).
</ResponseField>

<ResponseField name="event_title" type="string">
  Human-readable event title.
</ResponseField>

<ResponseField name="outcome_key" type="string">
  Outcome bucket key (e.g. `btc_15m`).
</ResponseField>

<ResponseField name="label" type="string">
  Short display label (e.g. `BTC 15m`).
</ResponseField>

<ResponseField name="timestamp" type="string">
  Opportunity snapshot time.
</ResponseField>

<ResponseField name="market_type" type="string">
  Market type, currently `binary`.
</ResponseField>

<ResponseField name="interval_code" type="string">
  Interval for the window when applicable.
</ResponseField>

<ResponseField name="resolution_time" type="string">
  Window settlement time.
</ResponseField>

<ResponseField name="kalshi_event_ticker" type="string">
  Matching Kalshi event ticker, when available.
</ResponseField>

<ResponseField name="polymarket_event_slug" type="string">
  Matching Polymarket event slug, when available.
</ResponseField>

<ResponseField name="kalshi" type="object">
  Kalshi leg snapshot: `market_ticker`, `yes_ask`, `no_ask`, `volume`,
  `volume_24h`, `open_interest`.
</ResponseField>

<ResponseField name="polymarket" type="object">
  Polymarket leg snapshot: `condition_id`, `yes_token_id`, `no_token_id`,
  `yes_ask`, `no_ask`, `volume`, `liquidity`.
</ResponseField>

<ResponseField name="buy_yes_market" type="string">
  Venue to buy YES (`kalshi` or `polymarket`).
</ResponseField>

<ResponseField name="buy_no_market" type="string">
  Venue to buy NO.
</ResponseField>

<ResponseField name="gross_cents" type="number">
  Gross edge in cents before fees.
</ResponseField>

<ResponseField name="fee_cents" type="number">
  Estimated taker fees in cents.
</ResponseField>

<ResponseField name="net_cents" type="number">
  Net edge after fees in cents.
</ResponseField>

<ResponseField name="roi_percent" type="number">
  Return on capital for the paired legs.
</ResponseField>

<ResponseField name="executable_size" type="number">
  Number of contracts executable at the shown edge.
</ResponseField>

<ResponseField name="max_profit_dollars" type="number">
  Maximum profit in dollars at the executable size.
</ResponseField>

<ResponseField name="yes_leg" type="object">
  Execution leg: `market`, `identifier`, `book_side`.
</ResponseField>

<ResponseField name="no_leg" type="object">
  Execution leg: `market`, `identifier`, `book_side`.
</ResponseField>

<ResponseField name="orderbook" type="object">
  Present only when orderbook depth is captured for the episode.
</ResponseField>

<ResponseField name="locked" type="boolean">
  `true` for masked preview rows. See below.
</ResponseField>

## Examples

### 15-minute opportunities with min edge

<CodeGroup>
  ```bash cURL theme={null}
  curl -H "X-API-Key: YOUR_API_KEY" \
    "https://api.probsights.com/v1/arbitrage/history?min_net_cents=2&interval_code=15m&limit=20"
  ```

  ```python Python theme={null}
  import requests

  response = requests.get(
      "https://api.probsights.com/v1/arbitrage/history",
      headers={"X-API-Key": "YOUR_API_KEY"},
      params={"min_net_cents": 2, "interval_code": "15m", "limit": 20},
  )
  page = response.json()
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://api.probsights.com/v1/arbitrage/history?min_net_cents=2&interval_code=15m&limit=20",
    { headers: { "X-API-Key": "YOUR_API_KEY" } }
  );
  const page = await response.json();
  ```
</CodeGroup>

### Paginate through the feed

<CodeGroup>
  ```bash cURL theme={null}
  curl -H "X-API-Key: YOUR_API_KEY" \
    "https://api.probsights.com/v1/arbitrage/history?limit=100&offset=100"
  ```
</CodeGroup>

Use `has_more` to keep paging; `total` is the filtered count. Keep `limit` fixed
across pages for stable `offset` math.

## Preview masking

Without a paid entitlement the scanner's history is masked server-side: the
opportunity shown first is `locked: false` with full prices and `net_cents`, and
every other row is `locked: true` with `net_cents: null` and both venue asks
removed. A **Trader** subscription — see [Authentication](/authentication) —
unlocks the full feed.

## Fee model

Arb legs are modeled as **taker** orders (buying at the ask):

| Exchange            | Formula (1 contract)         | Rounding                            |
| ------------------- | ---------------------------- | ----------------------------------- |
| Kalshi              | `0.07 × price × (1 − price)` | Round **up** to next cent           |
| Polymarket (crypto) | `0.07 × price × (1 − price)` | Round to 5 dp; min charge `0.00001` |
