You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(api-reference): document quality_tier on /opportunities/ev (#287)
Add the grader-certified quality_tier (A/B/C) field, live on /opportunities/ev
as of 2026-07-14 (sharp-api-go #1204). Covers the endpoint field reference, a
Quality Tier section with the A/B/C criteria + a real high-EV-but-B example, an
EV Calculation methodology section on the grading rationale, and the OpenAPI
schema (EVOpportunity.quality_tier, enum A/B/C, not required — omitted on
suspended rows). Documents it as output-only (no query filter). Bumps
openapi info.version 3.1.0 -> 3.2.0 with a CHANGELOG entry.
Co-authored-by: sharp-resolver[bot] <3736210+sharp-resolver[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,6 +6,10 @@ to bump. Every change to API paths or response schemas gets a one-line entry her
6
6
the [OpenAPI Version Check](.github/workflows/openapi-version-check.yml) CI job
7
7
enforces that a bump has a matching entry.
8
8
9
+
## 3.2.0 — 2026-07-14
10
+
11
+
- 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.
12
+
9
13
## 3.1.0 — 2026-06-02
10
14
11
15
- 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.
Copy file name to clipboardExpand all lines: content/en/api-reference/opportunities-ev.mdx
+28Lines changed: 28 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -345,6 +345,7 @@ No key supplied returns `missing_api_key`; a bad key returns `invalid_api_key`.
345
345
|`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. |
|`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. |
348
+
|`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. |
348
349
|`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. |
349
350
|`detected_at`| string | ISO 8601 timestamp when the +EV was first detected |
350
351
|`odds_probability`| number | Implied probability of `odds_american` (vig-included) |
@@ -431,6 +432,33 @@ When soft books are slow to update their odds after a market move, their implied
431
432
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.
432
433
</Callout>
433
434
435
+
## Quality Tier
436
+
437
+
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?"*
438
+
439
+
| Tier | Meaning | Criteria |
440
+
|------|---------|----------|
441
+
|**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. |
442
+
|**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. |
443
+
|**C**|**Informational**| Everything else, plus **any** exclusion-flagged segment regardless of EV. Disclosed as market intelligence, not a recommended bet. |
444
+
445
+
<Callouttype="info">
446
+
`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.
447
+
</Callout>
448
+
449
+
**A high `ev_percentage` does not guarantee tier A.** Two prematch, Pinnacle-anchored baseball totals from the *same* book illustrate why:
| 1st 5 Innings Total Runs — Over 5 | 7.35 |**A**| Segment unflagged |
454
+
| Total Runs — Over 9 | 7.94 |**B**|*Higher* EV, but the full-game `total_runs` segment is watch-flagged for historically weak results |
455
+
456
+
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.
457
+
458
+
<Callouttype="warning">
459
+
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.
460
+
</Callout>
461
+
434
462
## Kelly Criterion
435
463
436
464
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%.
Copy file name to clipboardExpand all lines: content/en/concepts/ev-calculation.mdx
+27Lines changed: 27 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -98,6 +98,33 @@ When soft books are slow to adjust, their odds imply a lower probability than re
98
98
SharpAPI's default threshold is 2% EV. Adjust with the `threshold` parameter.
99
99
100
100
101
+
## Quality Tiers
102
+
103
+
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.
104
+
105
+
### Why a tier, not just a number
106
+
107
+
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.
108
+
109
+
### The tiers
110
+
111
+
-**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.
112
+
-**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.
113
+
-**C — informational.** Everything else, plus any exclusion-flagged segment regardless of EV. Surfaced as market intelligence, not a recommended bet.
114
+
115
+
### Segment reliability
116
+
117
+
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:
118
+
119
+
- a **watch** flag caps a segment at **B** — its results are shaky but not disqualifying;
120
+
- 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.
121
+
122
+
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.
123
+
124
+
> **Warning:**
125
+
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.
Copy file name to clipboardExpand all lines: public/openapi.json
+11-1Lines changed: 11 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
"openapi": "3.1.0",
3
3
"info": {
4
4
"title": "SharpAPI",
5
-
"version": "3.1.0",
5
+
"version": "3.2.0",
6
6
"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",
"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).",
0 commit comments