Skip to content

Commit 8a021e2

Browse files
Mlaz-codeclaude
andauthored
docs(conventions): add Conditional requests and ETag section (#194)
Documents the ETag + If-None-Match/304 support shipped in sharp-api-go PR #53 + nginx PR #55. Covers: - Supported endpoints (every cached GET: odds, events, sportsbooks, sports, leagues, markets, teams, opportunities) - Strong-ETag semantics (xxhash of body, quoted hex) - Example request/response flow for 200 → 304 revalidation - Wildcard If-None-Match: * matching - Cache-Control: private, max-age=0, must-revalidate meaning - Tier-scoped ETag notes (free vs pro won't cross-match) - Client-library hints (requests-cache, undici + CacheStore) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 84d08b5 commit 8a021e2

1 file changed

Lines changed: 36 additions & 0 deletions

File tree

content/en/api-reference/conventions.mdx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,42 @@ Every authenticated response includes:
147147
| `X-Data-Delay` | Odds delay in seconds for your tier (`0` = real-time). |
148148
| `X-Request-Id` | Unique request identifier — include this in support requests. |
149149

150+
## Conditional requests and ETag
151+
152+
Every cacheable `200` response carries a strong `ETag` header — a content hash of the response body. Send it back on your next request via `If-None-Match` to get a `304 Not Modified` with no body when the content hasn't changed. Saves bandwidth on large payloads (`/odds` responses can be multi-MB) and lets you poll more aggressively without re-downloading unchanged data.
153+
154+
```http
155+
GET /api/v1/odds?sport=basketball HTTP/1.1
156+
Authorization: Bearer sk_...
157+
158+
HTTP/1.1 200 OK
159+
ETag: "9dc023776c4b382"
160+
Cache-Control: private, max-age=0, must-revalidate
161+
Content-Type: application/json
162+
163+
{ "data": [...], "updated_at": "..." }
164+
```
165+
166+
On the next poll, echo the `ETag`:
167+
168+
```http
169+
GET /api/v1/odds?sport=basketball HTTP/1.1
170+
Authorization: Bearer sk_...
171+
If-None-Match: "9dc023776c4b382"
172+
173+
HTTP/1.1 304 Not Modified
174+
ETag: "9dc023776c4b382"
175+
```
176+
177+
**Supported on** every cached GET: `/odds`, `/odds/delta`, `/odds/best`, `/odds/comparison`, `/events`, `/events/:id`, `/sportsbooks`, `/sports`, `/leagues`, `/markets`, `/teams`, `/opportunities/*`.
178+
179+
**Notes:**
180+
- ETag is **strong** — a byte-for-byte match of the response body. Two bytewise-identical responses always share an ETag; any change produces a new one.
181+
- `If-None-Match: *` always matches, so it forces a `304` whenever *any* cached response exists (useful for "is there anything new?" probes).
182+
- `Cache-Control: private, max-age=0, must-revalidate` signals that responses are per-caller — don't share them across users via a shared proxy.
183+
- ETags are **tier-scoped**. A `free`-tier ETag won't match a `pro`-tier request for the same URL because the responses are filtered differently. You only need to handle this if you change tiers mid-session.
184+
- Most HTTP clients handle `If-None-Match` automatically when you enable their cache (e.g. `requests-cache` in Python, `undici` + `CacheStore` in Node.js). For hand-rolled polling, cache the last ETag per URL in memory and send it on the next request.
185+
150186
## What this is *not*
151187

152188
- **No `success` field** on responses. HTTP status codes do that job.

0 commit comments

Comments
 (0)