Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ to bump. Every change to API paths or response schemas gets a one-line entry her
the [OpenAPI Version Check](.github/workflows/openapi-version-check.yml) CI job
enforces that a bump has a matching entry.

## 3.2.0 — 2026-07-14

- Add `quality_tier` to the `EVOpportunity` schema (`/opportunities/ev`) — a grader-certified quality label (`A` / `B` / `C`) stamped on every opportunity. `A` = corrected EV ≥ 4pp (≥ 6pp props), Pinnacle-anchored, prematch, and an unflagged segment (the consistently-positive graded-ROI class); `B` = EV ≥ 2pp (≥ 4pp props) not exclusion-flagged; `C` = everything else, plus any exclusion-flagged segment regardless of EV. Read-only (no query filter); omitted on suspended rows (`is_suspended`). Additive, backward-compatible. sharp-api-go #1204.

## 3.1.0 — 2026-06-02

- Add `player_id` to the `EVOpportunity` schema (`/opportunities/ev`) — the canonical cross-book player identifier resolved by atlas_players (e.g. `baseball_mlb_corbin_carroll`). Nullable and always present alongside `player_name`; `null` until the producer flips a book to active player-id capture. Consumers should group player props on `player_id` and fall back to `player_name`. Additive, backward-compatible. sharp-api-go #213.
Expand Down
28 changes: 28 additions & 0 deletions content/en/api-reference/opportunities-ev.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ No key supplied returns `missing_api_key`; a bad key returns `invalid_api_key`.
| `est_correction_s` | number\|null | Estimated seconds until the bet-side book corrects toward the sharp move (P50 follow-lag for this sportsbook+sport). `null` when no sample is available. |
| `warnings` | string[] | Data quality warnings (e.g., `POTENTIALLY_STALE_ODDS`, `LIVE_STALE_ODDS`, `SINGLE_SHARP_REF`, `SINGLE_SHARP_PERIOD`) |
| `single_sharp_period` | boolean | `true` when only one sharp book priced the line **and** the market is a period/derivative sub-slice (innings, halves, quarters, sets). Filterable flag for phantom-suspect period EV; mirrors the `SINGLE_SHARP_PERIOD` warning. |
| `quality_tier` | string\|undefined | Grader-certified quality label — `A` (bet-this), `B` (edge), or `C` (informational). Present on every opportunity; **omitted** on suspended rows (`is_suspended`). Output-only — there is **no** `quality_tier` query filter. See [Quality Tier](#quality-tier) below for the criteria. |
| `is_suspended` | boolean\|undefined | `true` (omitted otherwise) when the sharp reference is currently suspended live: the bet is still offerable but the edge is unknown, so `ev_percentage`, `fair_probability`, `no_vig_odds`, `kelly_percent`, and `market_width` are `null`ed rather than shown stale. `suspended_since` (Unix epoch seconds) accompanies it. |
| `detected_at` | string | ISO 8601 timestamp when the +EV was first detected |
| `odds_probability` | number | Implied probability of `odds_american` (vig-included) |
Expand Down Expand Up @@ -431,6 +432,33 @@ When soft books are slow to update their odds after a market move, their implied
We recommend setting `min_ev=2` for most use cases. Marginal EV (below 2%) can be eroded by line movement before you place the bet.
</Callout>

## Quality Tier

Every opportunity carries a `quality_tier` — a grader-certified `A` / `B` / `C` label that supersedes the rough [EV Thresholds](#ev-thresholds) heuristic above with evidence from SharpAPI's historical grading ledger. It answers *"how much should I trust this edge?"*, not just *"how big is the stated EV?"*

| Tier | Meaning | Criteria |
|------|---------|----------|
| **A** | **Bet this** | `ev_percentage` ≥ 4 (≥ 6 for player props), Pinnacle-anchored (`sharp_book: "pinnacle"`), **prematch** (`is_live: false`), and the `(book, sport, market)` segment is **not** flagged by our nightly reliability model. Historically the only class with consistently positive graded ROI. |
| **B** | **Edge** | `ev_percentage` ≥ 2 (≥ 4 for props) and the segment is not exclusion-flagged. Includes live, non-Pinnacle-anchored, and watch-flagged rows that clear the band — historically around break-even. |
| **C** | **Informational** | Everything else, plus **any** exclusion-flagged segment regardless of EV. Disclosed as market intelligence, not a recommended bet. |

<Callout type="info">
`quality_tier` is **output-only** — there is no `quality_tier` query parameter. Filter client-side. `min_ev` / `live` / `sportsbook` are only partial proxies: `min_ev` alone does not reproduce a tier, because the sharp anchor, prematch/live state, and segment reliability also gate it.
</Callout>

**A high `ev_percentage` does not guarantee tier A.** Two prematch, Pinnacle-anchored baseball totals from the *same* book illustrate why:

| Selection | `ev_percentage` | `quality_tier` | Why |
|-----------|-----------------|----------------|-----|
| 1st 5 Innings Total Runs — Over 5 | 7.35 | **A** | Segment unflagged |
| Total Runs — Over 9 | 7.94 | **B** | *Higher* EV, but the full-game `total_runs` segment is watch-flagged for historically weak results |

The higher-EV row is **B**, not A, because our reliability model has seen that segment underperform — exactly the "big EV number that's actually the artifact" the tiering is designed to demote. See [EV Calculation → Quality Tiers](/en/concepts/ev-calculation/#quality-tiers) for the grading rationale behind the bands.

<Callout type="warning">
A tier is **not** a profit guarantee. Even tier A carries variance (see [Kelly Criterion](#kelly-criterion) and the Variance Warning below). Suspended rows (`is_suspended: true`) carry **no** tier — their edge is unknown, so no quality claim is made.
</Callout>

## Kelly Criterion

The `kelly_percent` field is the optimal **percentage** of your bankroll (0–100) to wager according to the full Kelly criterion. A value of `2.1` means full Kelly recommends 2.1% of bankroll; a value of `34.4` means 34.4%.
Expand Down
27 changes: 27 additions & 0 deletions content/en/concepts/ev-calculation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,33 @@ When soft books are slow to adjust, their odds imply a lower probability than re
SharpAPI's default threshold is 2% EV. Adjust with the `threshold` parameter.


## Quality Tiers

The EV Thresholds table above is a rough heuristic. In practice **stated EV and realized results diverge**, so SharpAPI stamps every +EV opportunity with a grader-certified `quality_tier` (`A` / `B` / `C`) derived from its historical grading ledger — hundreds of thousands of picks scored against closing lines and settled outcomes.

### Why a tier, not just a number

When picks are graded against real results, realized ROI generally rises with stated EV — but the **low bands bleed**. The bulk of published volume sits in the sub-2% EV band, which has historically graded *negative* once vig and post-detection line movement are accounted for; the 2–4% band is roughly break-even. Durable, repeatable edge concentrates where a large stated EV is corroborated by the sharpest reference (Pinnacle), on a **prematch** market, in a segment that hasn't historically underperformed. Rather than delete the weaker bands — a real loss of market breadth — each opportunity is *labeled* with the tier its evidence supports.

### The tiers

- **A — bet this.** `ev_percentage` ≥ 4 (≥ 6 for player props), Pinnacle-anchored, prematch, and the `(book, sport, market)` segment is unflagged by the nightly reliability model. The class with consistently positive graded ROI.
- **B — edge.** `ev_percentage` ≥ 2 (≥ 4 for props) and the segment isn't exclusion-flagged. Includes live, non-Pinnacle-anchored, and watch-flagged rows — historically around break-even.
- **C — informational.** Everything else, plus any exclusion-flagged segment regardless of EV. Surfaced as market intelligence, not a recommended bet.

### Segment reliability

A tier is not EV alone. A nightly model scores each `(book, sport, market)` segment against its own graded history and can **cap** an otherwise-strong row:

- a **watch** flag caps a segment at **B** — its results are shaky but not disqualifying;
- an **exclude** flag forces **C** no matter how large the stated edge, because those cells have historically posted deeply negative ROI. A big EV number there is a red flag, not an opportunity.

This is why a prematch, Pinnacle-anchored row with a high `ev_percentage` can still land B or C. The bands and flags track the ledger: the reliability table is regenerated on a schedule, so tiers reflect current — not stale — evidence.

> **Warning:**
A tier reflects the *historical* graded performance of similar opportunities — it is **not** a guarantee for any single bet. Even tier A carries real variance; size with fractional Kelly and never bet more than you can afford to lose.


## Variance Warning

+EV doesn't guarantee profit on every bet:
Expand Down
2 changes: 1 addition & 1 deletion public/openapi-version.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "3.1.0",
"version": "3.2.0",
"x-generated-at": "2026-06-02T23:50:19+00:00",
"x-commit-sha": "6667df0"
}
12 changes: 11 additions & 1 deletion public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"openapi": "3.1.0",
"info": {
"title": "SharpAPI",
"version": "3.1.0",
"version": "3.2.0",
"description": "Real-time sports betting odds API with +EV detection, arbitrage, middles, and low-hold opportunities.\n\n## Spec Versioning\n\n`info.version` is bumped on every schema or path change. Minor version (`2.x.0`) for additive changes or breaking shape fixes that align the spec to the live response; major version (`x.0.0`) for backward-incompatible redesigns. Removed paths and renamed fields always bump the minor at minimum. Check `x-generated-at` and `x-commit-sha` for the build provenance of a given snapshot.\n\n## Authentication\n\nAll authenticated endpoints accept an API key via one of three methods:\n\n| Method | Header / Param | Use case |\n|--------|---------------|----------|\n| `X-API-Key` | `X-API-Key: sk_live_...` | Recommended for server-side |\n| `Authorization` | `Authorization: Bearer sk_live_...` | Standard Bearer token |\n| `api_key` query | `?api_key=sk_live_...` | SSE/EventSource (cannot set headers) |\n\n## Subscription Tiers\n\n| Tier | Rate Limit | Data Delay | Max Books | EV | Arb | Middles | Game State |\n|------|-----------|------------|-----------|-----|-----|---------|------------|\n| Free | 12/min | 60s | 2 (DK, FD) | - | - | - | - |\n| Hobby | 120/min | Real-time | 5 | - | Yes | - | - |\n| Pro | 300/min | Real-time | 15 | Yes | Yes | Yes | - |\n| Sharp | 1000/min | Real-time | All | Yes | Yes | Yes | - |\n| Enterprise | Custom | Real-time | All | Yes | Yes | Yes | Yes |\n\n## Rate Limit Headers\n\nEvery authenticated response includes:\n\n- `X-RateLimit-Limit` - Requests allowed per minute\n- `X-RateLimit-Remaining` - Requests remaining in current window\n- `X-RateLimit-Reset` - Unix timestamp when the window resets\n- `X-Data-Delay` - Odds delay in seconds for your tier (0 = real-time)\n- `X-Request-Id` - Unique request identifier for support\n\n## WebSocket Streaming\n\nThe WebSocket endpoint at `wss://ws.sharpapi.io` is documented separately in [`asyncapi.yaml`](./asyncapi.yaml) (AsyncAPI 3.0). OpenAPI 3.x cannot express WebSocket subprotocols and message channels, so the SSE endpoint (`/stream`) is the only stream covered by this document.\n\n## MCP Server\n\nThe `POST /mcp` endpoint is a Model Context Protocol server (JSON-RPC 2.0 over Streamable HTTP). Tools are self-described at runtime via `tools/list`, so it's documented as a setup guide rather than an OpenAPI path — see [`/sdks/mcp`](https://docs.sharpapi.io/sdks/mcp).\n",
"contact": {
"name": "SharpAPI Support",
Expand Down Expand Up @@ -4757,6 +4757,16 @@
"type": "string"
},
"description": "Warning flags (e.g., POTENTIALLY_STALE_ODDS, LIVE_STALE_ODDS, LOW_IMPLIED_TOTAL)"
},
"quality_tier": {
"type": "string",
"enum": [
"A",
"B",
"C"
],
"description": "Grader-certified quality label. A = ev_percentage >= 4 (>= 6 for player props), Pinnacle-anchored (sharp_book = pinnacle), prematch (is_live = false), and the (book, sport, market) segment is unflagged by the nightly reliability model — the only class with consistently positive graded ROI. B = ev_percentage >= 2 (>= 4 props) and the segment is not exclusion-flagged; includes live, non-Pinnacle-anchored, and watch-flagged rows that clear the band. C = everything else, plus any exclusion-flagged segment regardless of EV (disclosed as market intelligence, not a pick). A high ev_percentage alone does not imply A — the sharp anchor, prematch/live state, and the segment-reliability model also gate it. Omitted on suspended rows (is_suspended).",
"example": "A"
}
}
},
Expand Down