Free, read-only JSON API and iframable widgets for OFF SZN player profiles, composite rankings, commitments, news, and more. No API key required for the free tier. Attribution required. Versioned at /api/v1.
429 with code: rate_limited, a Retry-After header, and retryAfter (seconds) in the body.X-OFFSZN-API-Key) is reserved; free tier ignores it.limit defaults to 25 (max 100 on most routes); offset is capped at 10,000.total: 0, not 404s.| Method | Path | Description |
|---|---|---|
| GET | /api/v1/players | List players (card fields) |
| GET | /api/v1/players/:id | Single player + composite ranks |
| GET | /api/v1/players/:id/timeline | Recruiting timeline events |
| GET | /api/v1/players/:id/articles | News tagged to a player |
| GET | /api/v1/players/:id/pdf | One-pager PDF (binary; 30/min) |
| GET | /api/v1/rankings | Composite rankings feed |
| GET | /api/v1/commitments | Latest commitments |
| GET | /api/v1/news | Published headlines + summaries |
| GET | /api/v1/events | Tournaments / events window |
| GET | /api/v1/colleges | Colleges by commit volume |
| GET | /api/v1/scores | HS contest scores (AZ-sourced) |
| GET | /api/v1/team-rankings | Program recruiting class ranks |
| GET | /api/v1/transfer-portal | Portal entries |
| GET | /api/v1/alumni | Graduated / alumni prospects |
/api/v1/players/:idFetch a single player profile + their composite ranking. Public card fields only.
{
"apiVersion": "v1",
"data": {
"id": "cmo9lw9p00001jw047ke18g3e",
"firstName": "Jaden",
"lastName": "Smith",
"position": "SG",
"classYear": 2027,
"starRating": 5,
"state": "AZ",
"composite": {
"compositeRating": 0.985,
"nationalRank": 12,
"positionRank": 3,
"stateRank": 1
},
"url": "https://offsznhoops.com/players/cmo9lw9p00001jw047ke18g3e",
"attribution": "Data via OFF SZN Hoops · offsznhoops.com"
}
}/api/v1/rankingsList rankings, filterable by state, classYear, and position. Paginated via offset.
| Param | Description |
|---|---|
| state | 2-letter code, e.g. AZ |
| classYear | Class year, e.g. 2027 |
| position | PG, SG, SF, PF, C |
| limit | 1–100 (default 25) |
| offset | Pagination offset (max 10_000) |
{
"apiVersion": "v1",
"filters": { "state": "AZ", "classYear": 2027, "position": null },
"pagination": { "offset": 0, "limit": 25, "total": 87 },
"data": [
{
"rank": 12,
"positionRank": 3,
"stateRank": 1,
"composite": 0.985,
"player": {
"id": "cmo9lw9p00001jw047ke18g3e",
"firstName": "Jaden",
"lastName": "Smith",
"starRating": 5,
"position": "SG",
"classYear": 2027,
"state": "AZ",
"url": "https://offsznhoops.com/players/cmo9lw9p00001jw047ke18g3e"
}
}
],
"attribution": "Data via OFF SZN Hoops · offsznhoops.com"
}/api/v1/commitmentsLatest commitments (event-sourced). Estimated import dates surface as null date + isDateEstimated: true.
| Param | Description |
|---|---|
| state | 2-letter code |
| classYear | Class year filter |
| limit | 1–100 (default 25) |
| offset | Pagination offset |
{
"apiVersion": "v1",
"total": 42,
"limit": 10,
"offset": 0,
"data": [
{
"id": "ev_…",
"date": "2026-04-12T00:00:00.000Z",
"isDateEstimated": false,
"college": "Arizona",
"player": { "id": "cmo9lw9p00001jw047ke18g3e", "firstName": "Jaden", "lastName": "Smith", "url": "…" }
}
],
"attribution": "OFF SZN Hoops · offsznhoops.com"
}curl -sS "https://offsznhoops.com/api/v1/players/cmo9lw9p00001jw047ke18g3e"
curl -sS "https://offsznhoops.com/api/v1/rankings?state=AZ&classYear=2027&limit=10"
Errors share a stable JSON envelope on every /api/v1/* route (including PDF). The human string is always in error; structured clients should branch on code. Every error includes apiVersion, docs (/developers), CORS *, and Cache-Control: private, no-store so failures are never edge-cached.
| Status | code | When |
|---|---|---|
| 404 | not_found | Unknown player id (or other missing resource) |
| 404 | invalid_id | Malformed id — use the id from list/rankings feeds |
| 403 | LICENSE_MODULE_DISABLED | White-label install without the api_v1 module · may include module + tier |
| 429 | rate_limited | Over free-tier budget (JSON 60/min · PDF 30/min) · Retry-After header + retryAfter body (seconds) |
| 503 | unavailable | Transient backend fault · retry with backoff |
{
"apiVersion": "v1",
"error": "Player not found. Confirm the id via GET /api/v1/players or GET /api/v1/rankings.",
"code": "not_found",
"docs": "/developers"
}{
"apiVersion": "v1",
"error": "Rate limited. Free tier is 60 req/min.",
"code": "rate_limited",
"docs": "/developers",
"retryAfter": 13
}No secrets in responses. Errors never include stack traces, env values, or internal query details. Empty list results are 200 with total: 0, not errors.
Drop an iframe into any article. Widgets are noindex, carry attribution in the footer, and open profile links with target=_top so readers leave the iframe cleanly. Free-tier JSON under /api/v1/* is 60 req/min/IP (see Limits above); embed HTML pages are edge-cached (~5 min ISR) and are not the same rate-limit bucket.
Paste into CMS HTML or a static page. Attribution footer is built into each widget — do not strip the chrome.
/embed/rankings?state=AZ&classYear=2027<iframe src="https://offsznhoops.com/embed/rankings?state=AZ&classYear=2027" width="100%" height="520" style="border:0;max-width:100%" loading="lazy" title="OFF SZN rankings" ></iframe>
/embed/commitments?state=AZ<iframe src="https://offsznhoops.com/embed/commitments?state=AZ" width="100%" height="520" style="border:0;max-width:100%" loading="lazy" title="OFF SZN commitments" ></iframe>
/embed/player/cmo9lw9p00001jw047ke18g3e<iframe src="https://offsznhoops.com/embed/player/cmo9lw9p00001jw047ke18g3e" width="100%" height="320" style="border:0;max-width:560px" loading="lazy" title="OFF SZN player card" ></iframe>
Empty & error contract
GET /api/v1/rankings or GET /api/v1/players — sample below uses live id cmo9lw9p00001jw047ke18g3e (refreshed hourly).429 + code: rate_limited). PDF is 30/min. Embeds are HTML, not that JSON bucket./embed/rankingsTop-N rankings table. Omit state (or use ALL) for national. limit 5–25 (default 10). Empty filters show an open-site CTA — no invented rows.
<iframe src="https://offsznhoops.com/embed/rankings?state=AZ&classYear=2027" width="100%" height="520" style="border:0;max-width:100%" loading="lazy" title="OFF SZN rankings" ></iframe>
/embed/commitmentsLatest commitments ticker. state optional; limit 5–25. Estimated import dates never display as real commit days.
<iframe src="https://offsznhoops.com/embed/commitments?state=AZ" width="100%" height="520" style="border:0;max-width:100%" loading="lazy" title="OFF SZN commitments" ></iframe>
/embed/player/:idSingle-player card for articles and newsletters. height ~320 works well. Example id is a live top-ranked player (refreshed hourly).
<iframe src="https://offsznhoops.com/embed/player/cmo9lw9p00001jw047ke18g3e" width="100%" height="320" style="border:0;max-width:560px" loading="lazy" title="OFF SZN player card" ></iframe>
Free tier requires a visible attribution link to offsznhoops.com on the page or article using our data. API responses include an attribution string you can render verbatim. Embed widgets carry attribution natively.
The free tier is public and no-key. It is rate-limited and attribution-required. If you are redistributing data at product scale, need higher throughput, or want a named contact, reach out — free tier stays available either way.
/api/v1/* are 60 requests / minute / IP (enforced in shared withV1). Player PDF is 30 / minute / IP. Over budget returns 429 with code: rate_limited, Retry-After, and retryAfter (seconds)./embed/* already show a footer credit. If you build a custom UI from the API, keep a visible link to offsznhoops.com (or render the response attribution string). Do not strip embed chrome or re-skin widgets to hide the source.No invented partner tiers on this page — we only document limits the free tier actually enforces today.