Skip to content

Commit c86b7de

Browse files
Mlaz-codeCodexclaude
authored
docs: document is_active field on Odds (SHA-3803) (#255)
* 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) <noreply@anthropic.com> * docs: document odds:locked stream event (SHA-3803) 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) <noreply@anthropic.com> * 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) <noreply@anthropic.com> --------- Co-authored-by: Codex <codex@sharpapi.local> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 70581d5 commit c86b7de

5 files changed

Lines changed: 48 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ to bump. Every change to API paths or response schemas gets a one-line entry her
66
the [OpenAPI Version Check](.github/workflows/openapi-version-check.yml) CI job
77
enforces that a bump has a matching entry.
88

9+
## 2.2.0 — 2026-05-31
10+
11+
- Add `is_active` field to the `Odds` schema (`false` = market suspended/closed, price frozen; mirrors OpticOdds locked-odds; absent treated as `true`). SHA-3803.
12+
913
## 2.1.0 — 2026-05-21
1014

1115
- Align `/account` response schema with the live flat shape (#230).

content/en/api-reference/odds.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ X-Request-Id: req_abc123def456
329329
| `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). |
330330
| `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. |
331331
| `is_live` | boolean | Whether the event is currently live |
332+
| `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`. |
332333
| `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. |
333334
| `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. |
334335
| `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). |

content/en/api-reference/stream.mdx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ https://api.sharpapi.io/api/v1/stream?api_key=sk_live_your_key
4444

4545
| Channel | Events Delivered | Use Case |
4646
|---------|-----------------|----------|
47-
| `odds` | `snapshot`, `odds:update`, `odds:removed`, `heartbeat` | Track odds movements |
47+
| `odds` | `snapshot`, `odds:update`, `odds:locked`, `odds:removed`, `heartbeat` | Track odds movements |
4848
| `opportunities` | `snapshot`, `ev:detected/expired`, `arb:detected/expired`, `middles:detected/expired`, `low_hold:detected/expired`, `heartbeat` | Alert on opportunities |
4949
| `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. |
5050
| `all` | All event types | Full real-time picture |
@@ -132,8 +132,14 @@ data: {"odds":[{"id":"123456","odds_american":-150,"odds_decimal":1.667,"odds_pr
132132
| `odds_probability` | number | Updated implied probability (e.g. `0.6`) |
133133
| `line` | number \| null | Updated line/spread (e.g. `-3.5`), or `null` for moneyline |
134134
| `is_live` | boolean | Whether the event is currently live |
135+
| `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. |
136+
| `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. |
137+
| `is_alternate_line` | boolean | Positive-polarity sibling of `is_main_line` (mutually exclusive). |
138+
| `is_stale_pregame_price` | boolean | `true` when a live row still carries a pre-game price that hasn't moved since kickoff. |
135139
| `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/). |
136140

141+
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.
142+
137143
**Envelope fields:**
138144

139145
| Field | Type | Description |
@@ -223,6 +229,20 @@ id: evt_00050
223229
data: {"expired":["lowhold_abc123"]}
224230
```
225231

232+
### `odds:locked`
233+
234+
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.
235+
236+
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`.
237+
238+
```
239+
event: odds:locked
240+
id: evt_00052
241+
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}
242+
```
243+
244+
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.
245+
226246
### `odds:removed`
227247

228248
Odds removed by a sportsbook (e.g. market taken down, event settled). Only sent on `odds` or `all` channels.

content/en/api-reference/websocket.mdx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,23 @@ Incremental odds update from a single sportsbook.
283283
}
284284
```
285285

286+
#### `odds:locked`
287+
288+
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`.
289+
290+
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`.
291+
292+
```json
293+
{
294+
"type": "odds:locked",
295+
"seq": 48,
296+
"source": "pinnacle",
297+
"data": [ /* NormalizedOdds[] with is_active: false */ ],
298+
"count": 1,
299+
"timestamp": "2026-02-08T18:47:19.250Z"
300+
}
301+
```
302+
286303
#### `odds:removed`
287304

288305
Odds removed by a sportsbook (e.g. market taken down, event settled).

public/openapi.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"openapi": "3.1.0",
33
"info": {
44
"title": "SharpAPI",
5-
"version": "2.1.0",
5+
"version": "2.2.0",
66
"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",
77
"contact": {
88
"name": "SharpAPI Support",
@@ -4458,6 +4458,10 @@
44584458
"is_live": {
44594459
"type": "boolean"
44604460
},
4461+
"is_active": {
4462+
"type": "boolean",
4463+
"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)."
4464+
},
44614465
"timestamp": {
44624466
"type": "string",
44634467
"format": "date-time",

0 commit comments

Comments
 (0)