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.

## 2.3.0 — 2026-06-01

- Add `odds_changed_at` to the `Odds` schema — the canonical per-row freshness field (previously undocumented; also the only per-odd freshness timestamp OpticOdds exposes). Deprecate `last_seen_at`, `wire_received_at`, and the stale `timestamp` prop (`deprecated: true`) — being internalized; read `odds_changed_at` for freshness. Removal tracked in sharp-api-go #743.

## 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.
Expand Down
2 changes: 1 addition & 1 deletion content/en/api-reference/odds-batch.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ Each item in the `data.events` array is an event object with nested odds:
| `odds_american` | number | American odds |
| `odds_decimal` | number | Decimal odds |
| `line` | number \| null | Line value |
| `last_seen_at` | string | ISO 8601 timestamp when our pipeline last observed this row. Use this as your pipeline freshness signal. |
| `last_seen_at` | string | **⚠️ Deprecated** — being internalized; will be removed in a future release. Liveness heartbeat (advances every cycle even when the price is unchanged), not a price-freshness signal. Read `odds_changed_at` for freshness. |
| `odds_changed_at` | string | ISO 8601 timestamp of when the price, line, or `is_live` flag last actually changed. Sportsbook-provided when available; on Pinnacle it carries forward across unchanged refreshes — see [Understanding Pinnacle's `odds_changed_at`](/en/concepts/pinnacle-odds-changed-at/). |

## Use Cases
Expand Down
2 changes: 1 addition & 1 deletion content/en/api-reference/odds-best.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ X-Request-Id: req_best_789xyz
| `all_books[].line` | number \| null | Line at this sportsbook |
| `all_books[].last_seen_at` | string | When our pipeline last observed this book's row — pipeline freshness signal |
| `all_books[].odds_changed_at` | string | When this book's price, line, or `is_live` flag last actually changed. On Pinnacle, carries forward across unchanged refreshes — see [Understanding Pinnacle's `odds_changed_at`](/en/concepts/pinnacle-odds-changed-at/). |
| `last_seen_at` | string | ISO 8601 timestamp of the best odds determination |
| `last_seen_at` | string | **⚠️ Deprecated** — being internalized; will be removed in a future release. Liveness heartbeat for the row (advances every cycle even when the price is unchanged), not a price-freshness signal. Read `odds_changed_at` for freshness. |
| `player_name` | string\|undefined | Player name (player prop markets only) |
| `stat_category` | string\|undefined | Stat category, e.g. `points`, `rebounds` (player prop markets only) |

Expand Down
2 changes: 1 addition & 1 deletion content/en/api-reference/odds-comparison.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ Each entry in the `books` object:
|-------|------|-------------|
| `odds_american` | number | American odds |
| `odds_decimal` | number | Decimal odds |
| `last_seen_at` | string | When our pipeline last observed this book's row — pipeline freshness signal |
| `last_seen_at` | string | **⚠️ Deprecated** — being internalized; will be removed in a future release. Liveness heartbeat (advances every cycle even when the price is unchanged), not a price-freshness signal. Read `odds_changed_at` for freshness. |
| `odds_changed_at` | string | When this book's price, line, or `is_live` flag last actually changed. On Pinnacle, carries forward across unchanged refreshes — see [Understanding Pinnacle's `odds_changed_at`](/en/concepts/pinnacle-odds-changed-at/). |

## Understanding Hold
Expand Down
6 changes: 3 additions & 3 deletions content/en/api-reference/odds.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,9 @@ X-Request-Id: req_abc123def456
| `is_alternate_line` | boolean | `true` when this row's `line` differs from the canonical main line for its `(event, market_type, selection)` group. `false` for the consensus main line, for no-line markets (moneyline, outright), and for rows whose cohort hasn't been published yet (cold start, brand-new event). See `is_main_line` for the positive-polarity sibling that disambiguates "main" from "cohort-pending". |
| `is_main_line` | boolean | `true` when this row's `line` equals the canonical main line for its `(event, market_type, selection)` group, AND for no-line markets (moneyline, outright — always main by definition). `false` for alt-line rows and for rows whose cohort hasn't been published yet. **Mutually exclusive with `is_alternate_line`:** both true never coexists; both false is the cohort-pending state. Filter for `is_main_line=true` when you want a positive "main lines only" view that excludes cohort-pending rows. Also accepted as a query filter on [`/odds/best?is_main_line=true`](/en/api-reference/odds-best/). |
| `event_start_time` | string | ISO 8601 event start time |
| `last_seen_at` | string | ISO 8601 timestamp of our adapter's last observation of this row. Advances every ingest cycle even when the content is unchanged — use as a heartbeat that the row is still being maintained, not as an ingest-latency benchmark. |
| `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. |
| `last_seen_at` | string | **⚠️ Deprecated** — being internalized; will be removed in a future release. Liveness heartbeat: advances every ingest cycle even when the price is unchanged, so it is *not* a price-freshness signal. Read `odds_changed_at` for freshness. |
| `wire_received_at` | string\|undefined | **⚠️ Deprecated** — being internalized; will be removed in a future release. SharpAPI pipeline-arrival stamp for ingest-latency benchmarking only — not a betting signal. Read `odds_changed_at` for price freshness. |
| `odds_changed_at` | string | **The freshness field to read.** Best-available last-price-change time: the sportsbook's own source-update timestamp when provided, otherwise when SharpAPI first detected a price/line/`is_live` change — carried forward across unchanged refreshes (see [Understanding Pinnacle's `odds_changed_at`](/en/concepts/pinnacle-odds-changed-at/)). |
| `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. |
Expand Down
19 changes: 16 additions & 3 deletions 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": "2.2.0",
"version": "2.3.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 @@ -4462,15 +4462,28 @@
"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)."
},
"odds_changed_at": {
"type": "string",
"format": "date-time",
"description": "Best-available last-price-change time for this line: the sportsbook's own source-update timestamp when provided, otherwise when SharpAPI first detected a price/line/is_live change — carried forward across unchanged refreshes. This is the freshness field to read."
},
"timestamp": {
"type": "string",
"format": "date-time",
"description": "When these odds were last updated"
"deprecated": true,
"description": "DEPRECATED — not emitted on the odds response; read `odds_changed_at` for price freshness."
},
"last_seen_at": {
"type": "string",
"format": "date-time",
"deprecated": true,
"description": "DEPRECATED (being internalized, will be removed in a future release): liveness heartbeat — advances every ingest cycle even when the price is unchanged, so it is NOT a price-freshness signal. Read `odds_changed_at` instead."
},
"wire_received_at": {
"type": "string",
"format": "date-time",
"description": "Pipeline-arrival stamp — when sharp-api-go last observed a content-hash change for this row. Distinct from `last_seen_at` (adapter observation) and `odds_changed_at` (sportsbook's own source update)."
"deprecated": true,
"description": "DEPRECATED (being internalized, will be removed in a future release): SharpAPI pipeline-arrival stamp for ingest-latency benchmarking only — not a betting/price-freshness signal. Read `odds_changed_at` for price freshness."
},
"event_uuid": {
"type": "string",
Expand Down