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

# ON Sharp

> A single consensus line from the sharpest, highest-limit books in the market, re-priced at a fixed margin and streamed in real time. Built for closing line value, fair prices and steam detection.

## What is ON Sharp?

ON Sharp is a bookmaker computed by odds-api.io. You cannot bet at it. It is a reference price.

We take the prices of the sharpest, highest-limit books in the market and build one line from them:

1. Each contributing book is de-vigged, so its margin is removed.
2. The fair probabilities are blended. Books that lead price discovery get more weight.
3. The blended line is re-priced at a fixed, known margin.

A line is only published when enough of the panel quotes that exact line. You never receive a single book under a new name.

In the API, ON Sharp is a bookmaker named exactly `ON Sharp`. It appears next to every other bookmaker in `/v3/odds`, on the WebSocket and in `/v3/odds/movements`.

<CardGroup cols={2}>
  <Card title="Closing line value" icon="flag-checkered">
    Grade every bet against the sharp close, measured in probability
  </Card>

  <Card title="Fair prices" icon="scale-balanced">
    Strip a known margin exactly. No guessing which de-vig method to use
  </Card>

  <Card title="Steam detection" icon="bolt">
    A move in ON Sharp means the sharp market moved, not one book
  </Card>

  <Card title="Model calibration" icon="chart-line">
    Benchmark your model against the market's best estimate
  </Card>
</CardGroup>

## The Fixed Margin

Every bookmaker prices a little above 100% probability. That excess is its margin, and at most books it changes by league, market and time of day. ON Sharp's margin never changes:

| Market type                     | Margin | Implied probabilities sum to |
| ------------------------------- | ------ | ---------------------------- |
| Two-way (home/away, over/under) | 2%     | `1.020`                      |
| Three-way (home/draw/away)      | 2.5%   | `1.025`                      |

Three-way markets are `ML` and `ML HT` in football. Every other ON Sharp market is two-way, including `ML` in basketball, tennis, ice hockey, American football, baseball and esports.

Because the margin is fixed, a price only moves when the sharp market's view of the probability moves. A book widening its margin before team news does not move ON Sharp.

### Stripping the margin

The margin is applied proportionally, so the fair probability is a single division:

```text theme={null}
Two-way:    fair probability = (1 / odds) / 1.02
Three-way:  fair probability = (1 / odds) / 1.025
Fair odds = 1 / fair probability
```

```python theme={null}
# Two-way: Spread 0.25, home 1.885, away 2.042
p_home = (1 / 1.885) / 1.02   # 0.520
p_away = (1 / 2.042) / 1.02   # 0.480

# Three-way (football ML): home 3.613, draw 3.902, away 2.033
p_home = (1 / 3.613) / 1.025  # 0.270
p_draw = (1 / 3.902) / 1.025  # 0.250
p_away = (1 / 2.033) / 1.025  # 0.480
```

<Note>
  Prices are rounded to three decimals, so the implied probabilities sum to 1.020 or 1.025 within rounding.
</Note>

## Coverage

ON Sharp is **prematch only**. A line appears once the sharp market prices it, and it is removed at kickoff. There are no in-play ON Sharp odds.

**Sports:** Football, Basketball, Tennis, American Football, Baseball, Ice Hockey, Esports.

**Markets:**

| Market                                                                       | Where it applies  |
| ---------------------------------------------------------------------------- | ----------------- |
| `ML`, `Spread`, `Totals`                                                     | All seven sports  |
| `ML HT`, `Spread HT`, `Totals HT`                                            | First half        |
| `Corners Spread`, `Corners Totals`, `Corners Spread HT`, `Corners Totals HT` | Football          |
| `Spread (Games)`, `Totals (Games)`                                           | Tennis game lines |
| `First 5 Innings ML`, `First 5 Innings Spread`, `First 5 Innings Totals`     | Baseball          |

Spreads and totals include every alternative line the panel agrees on, not only the main line. Player props are not included.

## Getting Access

ON Sharp is available on all **paid plans**. It is not available on the free plan.

1. Open the [dashboard](https://odds-api.io) and add **ON Sharp** to your bookmaker selection.
2. Request it by name, `ON Sharp`, like any other bookmaker.

You can also add it through the API with `PUT /v3/bookmakers/selected/select`.

## REST Example

Request ON Sharp on its own, or alongside the books you already compare. The name contains a space, so URL-encode it (`ON%20Sharp`) when you build the URL by hand.

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://api.odds-api.io/v3/odds?apiKey=YOUR_API_KEY&eventId=61300607&bookmakers=ON%20Sharp&markets=ML,Spread,Totals"
  ```

  ```javascript JavaScript theme={null}
  const apiKey = process.env.ODDS_API_KEY;

  const params = new URLSearchParams({
    apiKey,
    eventId: '61300607',
    bookmakers: 'ON Sharp',
    markets: 'ML,Spread,Totals'
  });

  const event = await fetch(`https://api.odds-api.io/v3/odds?${params}`).then(r => r.json());

  const ml = event.bookmakers['ON Sharp'].find(m => m.name === 'ML');
  const { home, draw, away } = ml.odds[0];
  const fairHome = (1 / Number(home)) / 1.025;
  console.log(`Fair home probability: ${fairHome.toFixed(3)}`);
  ```

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

  api_key = os.environ['ODDS_API_KEY']

  event = requests.get(
      'https://api.odds-api.io/v3/odds',
      params={
          'apiKey': api_key,
          'eventId': 61300607,
          'bookmakers': 'ON Sharp',
          'markets': 'ML,Spread,Totals',
      },
  ).json()

  ml = next(m for m in event['bookmakers']['ON Sharp'] if m['name'] == 'ML')
  fair_home = (1 / float(ml['odds'][0]['home'])) / 1.025
  print(f"Fair home probability: {fair_home:.3f}")
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "id": 61300607,
  "home": "Brentford FC",
  "away": "Manchester United",
  "date": "2026-09-27T11:30:00Z",
  "status": "pending",
  "sport": { "name": "Football", "slug": "football" },
  "league": { "name": "England - Premier League", "slug": "england-premier-league" },
  "bookmakers": {
    "ON Sharp": [
      {
        "name": "ML",
        "updatedAt": "2026-09-27T09:14:02Z",
        "odds": [
          { "home": "3.613", "draw": "3.902", "away": "2.033" }
        ]
      },
      {
        "name": "Spread",
        "updatedAt": "2026-09-27T09:14:02Z",
        "odds": [
          { "hdp": 0.25, "home": "1.885", "away": "2.042" },
          { "hdp": 0.5, "home": "1.690", "away": "2.334" }
        ]
      },
      {
        "name": "Totals",
        "updatedAt": "2026-09-27T09:13:57Z",
        "odds": [
          { "hdp": 2.5, "over": "1.720", "under": "2.280" },
          { "hdp": 2.75, "over": "1.922", "under": "2.001" }
        ]
      }
    ]
  }
}
```

The fields are the same as for every other bookmaker. The one difference: there is never a `max` stake limit on ON Sharp odds. See the [FAQ](#faq).

## WebSocket Example

ON Sharp is recomputed as soon as a contributing book moves, and the new line is pushed over the WebSocket, usually within a second or two of the move. Nothing new to integrate: it arrives on the normal `odds` channel with `bookie` set to `ON Sharp`.

The WebSocket sends updates for the bookmakers in your selection, so add ON Sharp there first. See the [WebSocket guide](/guides/websockets) for connection details, compression and reconnection.

```bash theme={null}
wss://api.odds-api.io/v3/ws?apiKey=YOUR_API_KEY&channels=odds&status=prematch&markets=ML,Spread,Totals&leagues=england-premier-league
```

<CodeGroup>
  ```javascript Node.js theme={null}
  const WebSocket = require('ws');

  const params = new URLSearchParams({
    apiKey: process.env.ODDS_API_KEY,
    channels: 'odds',
    status: 'prematch',
    markets: 'ML,Spread,Totals',
    leagues: 'england-premier-league'
  });

  const ws = new WebSocket(`wss://api.odds-api.io/v3/ws?${params}`);
  const onSharp = new Map();

  ws.on('message', (raw) => {
    const msg = JSON.parse(raw);
    if (msg.bookie !== 'ON Sharp') return;

    if (msg.type === 'created' || msg.type === 'updated') {
      // Complete current set of markets: replace, do not merge
      onSharp.set(msg.id, msg.markets);
    } else if (msg.type === 'deleted' || msg.type === 'no_markets') {
      onSharp.delete(msg.id);
    }
  });
  ```

  ```python Python theme={null}
  import asyncio
  import json
  import os
  from urllib.parse import urlencode

  import websockets

  params = urlencode({
      'apiKey': os.environ['ODDS_API_KEY'],
      'channels': 'odds',
      'status': 'prematch',
      'markets': 'ML,Spread,Totals',
      'leagues': 'england-premier-league',
  })

  on_sharp = {}

  async def main():
      async with websockets.connect(f"wss://api.odds-api.io/v3/ws?{params}") as ws:
          async for raw in ws:
              msg = json.loads(raw)
              if msg.get('bookie') != 'ON Sharp':
                  continue
              if msg['type'] in ('created', 'updated'):
                  # Complete current set of markets: replace, do not merge
                  on_sharp[msg['id']] = msg['markets']
              elif msg['type'] in ('deleted', 'no_markets'):
                  on_sharp.pop(msg['id'], None)

  asyncio.run(main())
  ```
</CodeGroup>

A message looks like this:

```json theme={null}
{
  "type": "updated",
  "seq": 482917,
  "timestamp": 1790500442,
  "id": "61300607",
  "bookie": "ON Sharp",
  "markets": [
    {
      "name": "ML",
      "updatedAt": "2026-09-27T09:14:02Z",
      "odds": [{ "home": "3.613", "draw": "3.902", "away": "2.033" }]
    },
    {
      "name": "Totals",
      "updatedAt": "2026-09-27T09:13:57Z",
      "odds": [
        { "hdp": 2.5, "over": "1.720", "under": "2.280" },
        { "hdp": 2.75, "over": "1.922", "under": "2.001" }
      ]
    }
  ]
}
```

<Warning>
  **Replace, don't merge.** Each `updated` message carries the complete set of ON Sharp markets for that event. When the panel stops agreeing on a line, that line is missing from the next message. If you merge, you keep quoting a line ON Sharp no longer publishes.
</Warning>

<Note>
  ON Sharp lines are removed at kickoff. On a `status=prematch` connection the removal may not reach you once the event has started, so drop stored ON Sharp lines yourself when the event's `date` passes.
</Note>

## Movement History

Every ON Sharp change is recorded from the moment a line opens. Fetch the history with `/v3/odds/movements`:

```bash theme={null}
curl "https://api.odds-api.io/v3/odds/movements?apiKey=YOUR_API_KEY&eventId=61300607&bookmaker=ON%20Sharp&market=ML"
```

```json theme={null}
{
  "eventid": "61300607",
  "bookmaker": "ON Sharp",
  "opening": { "timestamp": 1790265600000, "home": 3.613, "draw": 3.902, "away": 2.033 },
  "movements": [
    { "timestamp": 1790265600000, "home": 3.613, "draw": 3.902, "away": 2.033 },
    { "timestamp": 1790420000000, "home": 3.509, "draw": 3.902, "away": 2.067 },
    { "timestamp": 1790507100000, "home": 3.412, "draw": 3.902, "away": 2.103 }
  ]
}
```

| Parameter    | Description                                                 |
| ------------ | ----------------------------------------------------------- |
| `eventId`    | Event ID                                                    |
| `bookmaker`  | `ON Sharp`                                                  |
| `market`     | Market name, e.g. `ML`, `Spread`, `Totals`                  |
| `marketLine` | The line, e.g. `2.5`. Required for every market except `ML` |

A few things to know about the response:

* `timestamp` is Unix time in **milliseconds**.
* `movements` is ordered oldest first. The last entry is the latest price.
* For `Spread` and `Totals`, each entry also has `hdp` (the line you asked for). On `Totals`, the over price is in `home` and the under price is in `away`.
* There is no `max` on ON Sharp entries.

## Worked Example: Closing Line Value

Closing line value (CLV) compares the price you took with the sharp market's final price. Beating the close consistently is the most reliable sign that a betting process has an edge. ON Sharp makes the close easy to measure, because its margin is known.

<Steps>
  <Step title="Take the bet">
    You back Brentford at **3.90** with a recreational book. At that moment ON Sharp's home price is 3.613.
  </Step>

  <Step title="Fetch the close">
    After kickoff, call `/v3/odds/movements` with `bookmaker=ON Sharp` and `market=ML`. Take the last entry with a `timestamp` before the event's `date`. Here that is home **3.412**.
  </Step>

  <Step title="Strip the margin">
    Football `ML` is three-way, so divide by 1.025. The closing fair probability is `(1 / 3.412) / 1.025 = 0.286`.
  </Step>

  <Step title="Grade the bet">
    Your price of 3.90 implies `1 / 3.90 = 0.256`. You paid 0.256 for an outcome the sharp market closed at 0.286. That is about 3 points of CLV, or an expected value of `3.90 × 0.286 - 1 = +11.5%` against the close.
  </Step>
</Steps>

```python theme={null}
import os
import requests
from datetime import datetime

api_key = os.environ['ODDS_API_KEY']
event_id = 61300607
your_odds = 3.90

event = requests.get(
    'https://api.odds-api.io/v3/events/61300607',
    params={'apiKey': api_key},
).json()
kickoff_ms = datetime.fromisoformat(event['date'].replace('Z', '+00:00')).timestamp() * 1000

history = requests.get(
    'https://api.odds-api.io/v3/odds/movements',
    params={'apiKey': api_key, 'eventId': event_id, 'bookmaker': 'ON Sharp', 'market': 'ML'},
).json()

close = [m for m in history['movements'] if m['timestamp'] < kickoff_ms][-1]
fair_close = (1 / close['home']) / 1.025

clv_points = fair_close - 1 / your_odds
ev_vs_close = your_odds * fair_close - 1
print(f"CLV: {clv_points * 100:+.1f} points, EV vs close: {ev_vs_close:+.1%}")
```

## Other Uses

### Value screens

Compare a soft book's price with ON Sharp's fair price. If `bookOdds × fairProbability - 1` is positive, the book is paying more than the sharp market thinks the outcome is worth.

### Steam detection

A single sharp book can move for its own reasons: a limit was hit, it is testing a price, or it changed its margin. ON Sharp only moves when the blended view of the panel moves. Watch ON Sharp on the WebSocket, or poll its movement history, and treat a sharp move as a signal to check books that have not adjusted yet.

### Model calibration

Log ON Sharp's closing fair probabilities next to your model's probabilities and the results. The gap between your model and the close tells you where it is miscalibrated.

## FAQ

<AccordionGroup>
  <Accordion title="Why is there no stake limit?">
    A stake limit describes a bet you can place. Nobody offers the ON Sharp price, so there is no limit to report. The `max` field is always empty for ON Sharp and is left out of the response.
  </Accordion>

  <Accordion title="Which books make up ON Sharp?">
    We do not publish the composition. We publish the method: each contributing book is de-vigged, fair probabilities are blended with weights favouring the books that lead price discovery, and the fixed margin is applied.
  </Accordion>

  <Accordion title="Why is a line missing for an event?">
    A line is only published when enough of the panel quotes that exact line. If the sharp books disagree on the handicap, or only one of them prices it, ON Sharp shows no line. No line is better than a wrong one. It is also missing once the event has started, because ON Sharp is prematch only.
  </Accordion>

  <Accordion title="Does ON Sharp have in-play odds?">
    No. ON Sharp is prematch only, and every line is removed at kickoff. Use the closing entry in the movement history to keep the final price.
  </Accordion>

  <Accordion title="Can I use it on the free plan?">
    No. ON Sharp is available on all paid plans. Add it to your bookmaker selection in the dashboard.
  </Accordion>

  <Accordion title="How fast does it update?">
    ON Sharp is recomputed as soon as a contributing book moves and pushed straight to WebSocket subscribers, usually within a second or two.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Value Bets" icon="chart-line" href="/guides/value-bets">
    Find positive expected value against the sharp market
  </Card>

  <Card title="Dropping Odds" icon="arrow-trend-down" href="/guides/dropping-odds">
    Track significant moves from sharp bookmakers
  </Card>

  <Card title="WebSocket" icon="bolt" href="/guides/websockets">
    Stream ON Sharp in real time
  </Card>

  <Card title="Historical Data" icon="clock-rotate-left" href="/guides/historical">
    Closing lines for backtesting
  </Card>
</CardGroup>
