Skip to content

Commit af7bb71

Browse files
Mlaz-codeclaude
andcommitted
docs(conventions): align envelopes with server + pin conventions doc
The OpenAPI spec declared a SuccessEnvelope shape ({success: true, data, meta: {updated_at}}) on 22 endpoints and a PaginatedEnvelope variant on 10 more. Neither shape was ever emitted — the server returns {data, updated_at} (non-paginated) or {data, pagination, updated_at} (paginated), with endpoint-specific extras like `overflow`, `missing`, or `removed`. Aligning to server reality: - Drop SuccessEnvelope entirely. Add a new DataEnvelope — {data, updated_at} — referenced by the 22 non-paginated endpoints. - Fix PaginatedEnvelope in place: remove the `success` requirement and the aspirational nested `meta`. Keep it as {data, pagination, updated_at}. - Both schemas keep the allOf pattern, so per-endpoint schemas still override `data` with the concrete item type. The body-level `success` flag is a 2010s anti-pattern — Stripe, Twilio, GitHub, JSON:API, and GraphQL all omit it. HTTP status codes do that job. Also adds content/en/api-reference/conventions.mdx codifying the whole REST contract (envelope, error shape, timestamps, snake_case casing, canonical IDs, rate-limit headers) so future drift has a written baseline to CI against. Linked into the sidebar just after the overview. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 14b8887 commit af7bb71

3 files changed

Lines changed: 205 additions & 67 deletions

File tree

content/en/api-reference/_meta.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export default {
22
overview: "Overview",
3+
conventions: "Response Conventions",
34
"---Reference": {
45
type: "separator",
56
title: "Reference Data",
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
---
2+
description: "SharpAPI response conventions — envelope shapes, timestamps, error format, field casing, and the rules that every REST endpoint follows."
3+
---
4+
5+
import { Callout } from 'nextra/components'
6+
7+
# Response Conventions
8+
9+
SharpAPI returns predictable response shapes across every REST endpoint. This page codifies those conventions so you can build generic parsers, not per-endpoint special cases.
10+
11+
<Callout type="info">
12+
This page describes the REST conventions. SSE streams are covered in [SSE Stream](/en/api-reference/stream), and the bidirectional WebSocket protocol has its own [AsyncAPI 3.0 spec](/asyncapi.yaml).
13+
</Callout>
14+
15+
## HTTP status codes, not envelopes
16+
17+
The HTTP status code is the source of truth for success or failure. Response bodies do **not** carry a `success` flag — it would duplicate information already on `response.status` and invites clients to check the wrong place.
18+
19+
| Status | Meaning |
20+
|---|---|
21+
| `200` | Success with a body |
22+
| `201` | Created (API key creation) |
23+
| `204` | Success, no body (key deletion) |
24+
| `302` | Redirect (deeplinks) |
25+
| `400` | Validation error — client-side bug |
26+
| `401` | Missing or invalid API key |
27+
| `403` | Tier doesn't include the requested feature |
28+
| `404` | Resource doesn't exist (or opaque ID didn't match) |
29+
| `429` | Rate limit exceeded |
30+
| `5xx` | Upstream or internal server error |
31+
32+
## Success envelope shapes
33+
34+
Two top-level shapes. Both put `data` first and `updated_at` last, with an optional pagination block in between.
35+
36+
### Non-paginated response
37+
38+
Used for singletons, reference data, and summary endpoints. `data` is whatever the endpoint returns — a single resource, an array, or a map.
39+
40+
```json
41+
{
42+
"data": { ... },
43+
"updated_at": "2026-04-16T19:29:38.920698424Z"
44+
}
45+
```
46+
47+
### Paginated list response
48+
49+
Used for endpoints that support `limit` / `offset` / `cursor`.
50+
51+
```json
52+
{
53+
"data": [ ... ],
54+
"pagination": {
55+
"limit": 50,
56+
"offset": 0,
57+
"count": 50,
58+
"total": 1247,
59+
"has_more": true,
60+
"next_offset": 50
61+
},
62+
"updated_at": "2026-04-16T19:29:38.920698424Z"
63+
}
64+
```
65+
66+
`count` is the length of the current `data` page, `total` is the full matching set. `next_offset` is present only when `has_more` is `true`.
67+
68+
### Endpoint-specific extensions
69+
70+
A handful of endpoints add extra top-level keys alongside `data`. These are additive — generic parsers that read `data` + `pagination` + `updated_at` keep working.
71+
72+
| Field | Emitted by | Meaning |
73+
|---|---|---|
74+
| `overflow: true` | `/odds`, `/odds/delta` | `total > 10_000` — consumer should pull a fresh snapshot via `/odds` instead of paginating through delta. |
75+
| `removed: [...]` | `/odds/delta` | IDs of odds removed since `?since=`. |
76+
| `missing: [...]` | `POST /odds/batch` | Event IDs that were requested but not found. |
77+
78+
## Error envelope
79+
80+
Every non-2xx response returns a single `error` object.
81+
82+
```json
83+
{
84+
"error": {
85+
"code": "rate_limited",
86+
"message": "Rate limit exceeded. Retry after 3 seconds.",
87+
"docs": "https://sharpapi.io/docs/rate-limits",
88+
"retryAfter": 3
89+
}
90+
}
91+
```
92+
93+
| Field | Always present? | Notes |
94+
|---|---|---|
95+
| `code` | Yes | Stable string. Check this, not the prose message. |
96+
| `message` | Yes | Human-readable English. Safe to surface to end users. |
97+
| `docs` | Sometimes | Link to the relevant doc page. |
98+
| `retryAfter` | On 429 / 5xx | Seconds until the client should retry. |
99+
| `tier` | On 403 | The tier that *would* unlock the endpoint. |
100+
101+
### Common error codes
102+
103+
`missing_api_key`, `invalid_api_key`, `validation_error`, `invalid_request`, `tier_restricted`, `rate_limited`, `too_many_streams`, `not_found`, `upstream_error`, `internal_error`.
104+
105+
## Timestamps
106+
107+
Every timestamp in a SharpAPI response is **RFC 3339 / ISO 8601 with UTC** — string form like `2026-04-16T19:29:38.920698424Z`. Nanosecond precision from the server; clients can safely parse with second or millisecond precision.
108+
109+
Two fields you'll see often:
110+
111+
- **`updated_at`** — when the server emitted the response. Top-level on every success response.
112+
- **`fetched_at`** — when the upstream sportsbook was last polled (present in odds payloads).
113+
114+
Older, stream-layer timestamps appear as Unix-seconds floats (`timestamp`) alongside their RFC 3339 counterpart (`ts`) on WebSocket messages.
115+
116+
## Field casing
117+
118+
**Snake case everywhere.** Fields inside `data`, `pagination`, `meta`, `error`, and every WebSocket / SSE message use `snake_case``event_id`, `market_type`, `profit_percent`, `detected_at`, `odds_american`, `stake_percent`.
119+
120+
The few `camelCase` leftovers you might spot (e.g. `eventIds` in a WebSocket `subscribe` payload *input*) are client-to-server request shapes. Everything server-to-client is snake_case.
121+
122+
## Canonical IDs
123+
124+
Most identifiers are stable, opaque, and joinable across endpoints.
125+
126+
| Field | Format | Example |
127+
|---|---|---|
128+
| `event_id` | `{league}_{home}_{away}_{date}` | `mlb_guardians_orioles_2026-04-16` |
129+
| `game_id` | Same as `event_id` in runner output | `nba_thunder_timberwolves_2026-03-15` |
130+
| `hash_id` (middles) | 16-char lowercase hex | `cc229ed94a2ee679` |
131+
| `betting_tool_id` | Human-readable canonical key | `middle:prematch:mlb_...:total_runs:...` |
132+
| API key | Prefixed token | `sk_live_...` |
133+
134+
Join `/splits` and `/odds` by `event_id`. Track an opportunity across polls by `hash_id`.
135+
136+
## Rate limit headers
137+
138+
Every authenticated response includes:
139+
140+
| Header | Meaning |
141+
|---|---|
142+
| `X-RateLimit-Limit` | Requests allowed per minute for your tier. |
143+
| `X-RateLimit-Remaining` | Requests remaining in the current window. |
144+
| `X-RateLimit-Reset` | Unix timestamp when the window resets. |
145+
| `X-Data-Delay` | Odds delay in seconds for your tier (`0` = real-time). |
146+
| `X-Request-Id` | Unique request identifier — include this in support requests. |
147+
148+
## What this is *not*
149+
150+
- **No `success` field** on responses. HTTP status codes do that job.
151+
- **No envelope-of-envelopes.** The top-level object is the envelope; `meta.updated_at` nesting is not used.
152+
- **No mixed casing.** Any response field is snake_case. Any WebSocket *input* field may be camelCase — everything else is snake_case.
153+
154+
## Machine-readable source of truth
155+
156+
- REST surface: [`openapi.json`](/openapi.json) (OpenAPI 3.1)
157+
- WebSocket surface: [`asyncapi.yaml`](/asyncapi.yaml) (AsyncAPI 3.0)
158+
159+
Both files are published as static assets and can be diffed in CI. If this page drifts from the spec, the spec wins — the spec is generated from the deployed server.

0 commit comments

Comments
 (0)