From 61495069c525716c9aa1dbba30adb2d4c43e5fb9 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 31 May 2026 08:18:34 -0400 Subject: [PATCH 1/3] docs: document is_active market-suspension field on Odds (SHA-3803) Add `is_active` to the OpenAPI Odds schema + the /odds field reference (false = market suspended/closed, price frozen; mirrors OpticOdds locked-odds; absent treated as true). Bumps openapi info.version 2.1.0 -> 2.2.0 with the matching CHANGELOG entry required by the openapi-version-check gate. Type: docs Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 4 ++++ content/en/api-reference/odds.mdx | 1 + public/openapi.json | 6 +++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e32cde0..fc45cf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. +## 2.2.0 — 2026-05-31 + +- Add `is_active` field to the `Odds` schema (`false` = market suspended/closed, price frozen; mirrors OpticOdds locked-odds; absent treated as `true`). SHA-3803. + ## 2.1.0 — 2026-05-21 - Align `/account` response schema with the live flat shape (#230). diff --git a/content/en/api-reference/odds.mdx b/content/en/api-reference/odds.mdx index 4efc2fd..51280b0 100644 --- a/content/en/api-reference/odds.mdx +++ b/content/en/api-reference/odds.mdx @@ -329,6 +329,7 @@ X-Request-Id: req_abc123def456 | `wire_received_at` | string\|undefined | ISO 8601 timestamp of when SharpAPI first observed a content change for this row, carried forward across subsequent unchanged refreshes. **Use this field for ingest-latency benchmarking** — it isolates SharpAPI's pipeline contribution from the sportsbook's source-side publish cadence. Omitted on cold-start rows where no prior observation exists. See [Understanding Pinnacle's `odds_changed_at`](/en/concepts/pinnacle-odds-changed-at/#benchmarking-pipeline-latency). | | `odds_changed_at` | string | ISO 8601 timestamp of the sportsbook's own source update for this line, when available. On Pinnacle, carries forward while the price/line/`is_live` flag are unchanged (see [Understanding Pinnacle's `odds_changed_at`](/en/concepts/pinnacle-odds-changed-at/)). Not suitable for SharpAPI pipeline-latency benchmarking — use `wire_received_at` for that. | | `is_live` | boolean | Whether the event is currently live | +| `is_active` | boolean | `true` (default) = market open and bettable; `false` = market suspended/closed with the price **frozen** at its last value (so you can grey it out rather than trust a stale price). Mirrors OpticOdds' `locked-odds`, but as a queryable field you can also filter on. Absent is treated as `true`. An active→suspended transition is pushed on the [odds stream](/en/api-reference/stream/) as an `odds:update` carrying `is_active: false`. | | `event_uuid` | string\|undefined | Stable canonical event UUID from the SharpAPI atlas, when the event is mapped. Where `event_id` carries the adapter's primary event identifier (often the originating sportsbook's), `event_uuid` is a feed-stable hash you can use for cross-feed joins. Absent for unmapped events. | | `external_event_id` | string\|undefined | The sportsbook's own native event ID, when distinct from `event_id`. Useful for round-tripping rows back to the sportsbook's UI or API. | | `deep_link` | string\|undefined | Resolver URL pointing to the sportsbook's event or bet-slip page. Pass `state=` (e.g. `state=nj`) on the request to route through state-specific subdomains for books that need them (BetMGM, Caesars, BetRivers). | diff --git a/public/openapi.json b/public/openapi.json index 322b842..d63bd23 100644 --- a/public/openapi.json +++ b/public/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "SharpAPI", - "version": "2.1.0", + "version": "2.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", @@ -4458,6 +4458,10 @@ "is_live": { "type": "boolean" }, + "is_active": { + "type": "boolean", + "description": "true (default) = market open and bettable; false = market suspended/closed with the price frozen. Mirrors OpticOdds locked-odds but exposed as a queryable field. Absent is treated as true. An active->suspended transition is emitted on the odds stream (odds:update with is_active=false)." + }, "timestamp": { "type": "string", "format": "date-time", From 95662628d0fc496d32632f215c9e6001f8f37ee9 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 31 May 2026 08:42:27 -0400 Subject: [PATCH 2/3] docs: document odds:locked stream event (SHA-3803) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the odds:locked SSE + WS event (sharp-api-go #725) to stream.mdx and websocket.mdx, plus an is_active row in the OddsDelta table. Supplementary OpticOdds-locked-odds analogue; suspended rows also ride odds:update with is_active=false. Content/MDX only — no openapi.json schema change, so no further info.version bump. Type: docs Co-Authored-By: Claude Opus 4.8 (1M context) --- content/en/api-reference/stream.mdx | 17 ++++++++++++++++- content/en/api-reference/websocket.mdx | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/content/en/api-reference/stream.mdx b/content/en/api-reference/stream.mdx index 7fa92dc..3edaa68 100644 --- a/content/en/api-reference/stream.mdx +++ b/content/en/api-reference/stream.mdx @@ -44,7 +44,7 @@ https://api.sharpapi.io/api/v1/stream?api_key=sk_live_your_key | Channel | Events Delivered | Use Case | |---------|-----------------|----------| -| `odds` | `snapshot`, `odds:update`, `odds:removed`, `heartbeat` | Track odds movements | +| `odds` | `snapshot`, `odds:update`, `odds:locked`, `odds:removed`, `heartbeat` | Track odds movements | | `opportunities` | `snapshot`, `ev:detected/expired`, `arb:detected/expired`, `middles:detected/expired`, `low_hold:detected/expired`, `heartbeat` | Alert on opportunities | | `gamestate` | `gamestate:snapshot`, `gamestate:update`, `gamestate:removed`, `heartbeat` | Live scores, periods, clocks, and situational data per event. **Enterprise tier only.** See [Live Game State](/api-reference/gamestate/) for the full field catalog. | | `all` | All event types | Full real-time picture | @@ -132,6 +132,7 @@ data: {"odds":[{"id":"123456","odds_american":-150,"odds_decimal":1.667,"odds_pr | `odds_probability` | number | Updated implied probability (e.g. `0.6`) | | `line` | number \| null | Updated line/spread (e.g. `-3.5`), or `null` for moneyline | | `is_live` | boolean | Whether the event is currently live | +| `is_active` | boolean | `true` = market open/bettable; `false` = market suspended/closed with the price frozen. A market suspending (e.g. after a goal) emits an `odds:update` with `is_active: false` — grey out the line rather than trusting the frozen price. See also the [`odds:locked`](#odds-locked) event. | | `odds_changed_at` | string | ISO 8601 timestamp of the sportsbook's own source update for this line, when available. On Pinnacle, carries forward while the underlying price/line/`is_live` flag are unchanged — see [Understanding Pinnacle's `odds_changed_at`](/en/concepts/pinnacle-odds-changed-at/). | **Envelope fields:** @@ -223,6 +224,20 @@ id: evt_00050 data: {"expired":["lowhold_abc123"]} ``` +### `odds:locked` + +Fired when a market is **suspended/closed** (e.g. after a goal, during a line move, or a late-game lockout) — the price is **frozen** but the selection is no longer bettable. Carries the suspended subset of the current delta, same payload shape as `odds:update`, with `is_active: false`. Only sent on `odds` or `all` channels. + +This is a 1:1 analogue of OpticOdds' `locked-odds` for easy migration. It is **supplementary** — the same rows also arrive in `odds:update` with `is_active: false`, so clients that already read `is_active` need not subscribe to `odds:locked` separately. Use it when you want a dedicated lock signal without parsing every `odds:update`. + +``` +event: odds:locked +id: evt_00052 +data: {"odds":[{"id":"123456","odds_american":-2500,"is_live":true,"is_active":false,"odds_changed_at":"2026-02-08T18:47:38Z"}],"count":1,"book":"pinnacle","partial":false} +``` + +A market re-opening emits a normal `odds:update` with `is_active: true` (and a fresh price). Markets a book **removes entirely** come through [`odds:removed`](#odds-removed) instead. + ### `odds:removed` Odds removed by a sportsbook (e.g. market taken down, event settled). Only sent on `odds` or `all` channels. diff --git a/content/en/api-reference/websocket.mdx b/content/en/api-reference/websocket.mdx index 69f6709..8fdbdaa 100644 --- a/content/en/api-reference/websocket.mdx +++ b/content/en/api-reference/websocket.mdx @@ -283,6 +283,23 @@ Incremental odds update from a single sportsbook. } ``` +#### `odds:locked` + +A market was **suspended/closed** (e.g. after a goal, a line move, or a late-game lockout) — the price is **frozen** and no longer bettable. Carries the suspended subset of the delta (same payload as `odds:update`, with `is_active: false`). A 1:1 analogue of OpticOdds' `locked-odds`. + +Supplementary: the same rows also arrive in `odds:update` with `is_active: false`, so clients reading `is_active` need not subscribe separately. A re-open emits a normal `odds:update` with `is_active: true`; a full removal comes through `odds:removed`. + +```json +{ + "type": "odds:locked", + "seq": 48, + "source": "pinnacle", + "data": [ /* NormalizedOdds[] with is_active: false */ ], + "count": 1, + "timestamp": "2026-02-08T18:47:19.250Z" +} +``` + #### `odds:removed` Odds removed by a sportsbook (e.g. market taken down, event settled). From e4640980ee52b8670a2e17fe68142e55c387a345 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 31 May 2026 10:04:49 -0400 Subject: [PATCH 3/3] docs: accurate OddsDelta field set + static-vs-dynamic guidance (SHA-3803) The OddsDelta table omitted dynamic fields that deltas carry (is_main_line, is_alternate_line, is_stale_pregame_price, exchange volumes) and didn't spell out the static-vs-dynamic split. Document the real compact delta shape and the "merge by id, never read static fields off a delta" contract. Pairs with the server-side delta compaction (sharp-api-go #726). Content/MDX only. Type: docs Co-Authored-By: Claude Opus 4.8 (1M context) --- content/en/api-reference/stream.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/content/en/api-reference/stream.mdx b/content/en/api-reference/stream.mdx index 3edaa68..f47431a 100644 --- a/content/en/api-reference/stream.mdx +++ b/content/en/api-reference/stream.mdx @@ -133,8 +133,13 @@ data: {"odds":[{"id":"123456","odds_american":-150,"odds_decimal":1.667,"odds_pr | `line` | number \| null | Updated line/spread (e.g. `-3.5`), or `null` for moneyline | | `is_live` | boolean | Whether the event is currently live | | `is_active` | boolean | `true` = market open/bettable; `false` = market suspended/closed with the price frozen. A market suspending (e.g. after a goal) emits an `odds:update` with `is_active: false` — grey out the line rather than trusting the frozen price. See also the [`odds:locked`](#odds-locked) event. | +| `is_main_line` | boolean | `true` when this line is the consensus main line for its market; `false` for alternate lines. Can flip as the main line moves. | +| `is_alternate_line` | boolean | Positive-polarity sibling of `is_main_line` (mutually exclusive). | +| `is_stale_pregame_price` | boolean | `true` when a live row still carries a pre-game price that hasn't moved since kickoff. | | `odds_changed_at` | string | ISO 8601 timestamp of the sportsbook's own source update for this line, when available. On Pinnacle, carries forward while the underlying price/line/`is_live` flag are unchanged — see [Understanding Pinnacle's `odds_changed_at`](/en/concepts/pinnacle-odds-changed-at/). | +Exchange books additionally carry the dynamic `volume`, `volume_24h`, `open_interest`, and `max_bet` fields when present. Everything else — `sportsbook`, `sport`, `league`, `home_team`, `away_team`, `market_type`, `selection`, `deep_link`, `event_start_time`, and the nested entity refs — is **static** and comes from the initial `snapshot`; merge each delta into your local map by `id` and never read a static field off a delta. + **Envelope fields:** | Field | Type | Description |