diff --git a/CHANGELOG.md b/CHANGELOG.md index fc45cf4..cea21e8 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.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. diff --git a/content/en/api-reference/odds-batch.mdx b/content/en/api-reference/odds-batch.mdx index c77b115..085e7b1 100644 --- a/content/en/api-reference/odds-batch.mdx +++ b/content/en/api-reference/odds-batch.mdx @@ -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 diff --git a/content/en/api-reference/odds-best.mdx b/content/en/api-reference/odds-best.mdx index 1e9b0e4..08523f7 100644 --- a/content/en/api-reference/odds-best.mdx +++ b/content/en/api-reference/odds-best.mdx @@ -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) | diff --git a/content/en/api-reference/odds-comparison.mdx b/content/en/api-reference/odds-comparison.mdx index 9552655..b31dde1 100644 --- a/content/en/api-reference/odds-comparison.mdx +++ b/content/en/api-reference/odds-comparison.mdx @@ -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 diff --git a/content/en/api-reference/odds.mdx b/content/en/api-reference/odds.mdx index 51280b0..6f54b3e 100644 --- a/content/en/api-reference/odds.mdx +++ b/content/en/api-reference/odds.mdx @@ -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. | diff --git a/public/openapi.json b/public/openapi.json index d63bd23..b4d3d4a 100644 --- a/public/openapi.json +++ b/public/openapi.json @@ -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", @@ -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",