1. Quick start
- Request a key (free for non-commercial use, or subscribe to a paid plan) and confirm your e-mail address.
- Once approved, sign in and create a key. It looks like
so_AbCd2345_…and is shown once. - Make your first request:
curl -H "Authorization: Bearer $STATSORB_KEY" \
https://www.statsorb.com/api/v1/leagues/nfl/seasons/2026/ratings?pretty
Python
import os, requests
r = requests.get(
"https://www.statsorb.com/api/v1/leagues/nfl/seasons/2026/ratings",
headers={"Authorization": f"Bearer {os.environ['STATSORB_KEY']}"},
timeout=30,
)
r.raise_for_status()
for team in r.json()["data"][:5]:
print(team["rank"]["thisWeek"], team["team"], team["rating"])
JavaScript (Node.js 18+)
const res = await fetch("https://www.statsorb.com/api/v1/leagues/nba/seasons/2026/standings", {
headers: { Authorization: `Bearer ${process.env.STATSORB_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).error.message);
const { data } = await res.json();
2. Authentication
Every data endpoint needs an API key, sent in a header:
Authorization: Bearer so_AbCd2345_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# or
X-API-Key: so_AbCd2345_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- Never put the key in the URL (
?api_key=is refused withkey_in_query) — URLs end up in logs and browser history. - Keep keys on your server. The API doesn't allow calls from web pages in other sites (no CORS), so a key in front-end code would only leak.
- Lock a key to your servers' IP addresses (see Managing keys) so it's useless if it ever leaks.
- All of your account's keys share its rate limits and daily quota.
3. Responses
Successful responses are JSON with two parts: data (what you asked for) and meta (league, season, when it was generated and the attribution line). Add ?pretty to any request for indented output.
{
"data": [ … ],
"meta": {
"generatedAt": "2026-10-21T19:04:11Z",
"attribution": "Data: Stats Orb (statsorb.com)",
"league": "nfl",
"season": 2026,
"plan": "free"
}
}
- Dates are
YYYY-MM-DD; times are UTC (ISO 8601). Probabilities are decimals from 0 to 1. - Fields that don't apply are
null(for example a score for a game not played yet). Records includetiesonly for leagues that have ties (NFL, NHL). - Every response carries an
X-Request-Idheader — include it if you contact us about a problem. - Ratings are recalculated as results come in; data is cached for about a minute, so polling faster than that returns the same numbers.
4. Rate limits & quotas
| Plan | Per minute | Per day (UTC) | Past the daily limit |
|---|---|---|---|
| Free (non-commercial) | 30 | 5,000 | Refused until 00:00 UTC |
| Developer | 120 | 100,000 | Allowed, billed at $9 per 1,000 requests (up to your spending cap) |
| Business | 300 | 1,000,000 | Allowed, billed at $9 per 1,000 requests (up to your spending cap) |
| Data license | 600 | 5,000,000 | By agreement |
The per-minute limit is a rolling 60-second window. Every metered response includes:
| X-RateLimit-Limit | Requests allowed per minute |
| X-RateLimit-Remaining | Requests left in the current window |
| X-RateLimit-Reset | Seconds until the window fully resets |
| X-Daily-Limit / X-Daily-Remaining | Your daily quota and what's left of it today (UTC) |
| X-Overage: true | This request was past your daily limit and counts as billable overage |
| Retry-After | On a 429, how many seconds to wait |
When you get a 429, wait for Retry-After seconds before retrying. Clients that keep sending requests while limited may be suspended automatically. Requests refused by a limit don’t count toward your quota; /usage and /status are free.
5. Leagues
GET/api/v1/leagues
The leagues and seasons available. League ids:
| id | League | Seasons |
|---|---|---|
| nfl | NFL | 1990–2026 |
| ncaaf | College Football | 1990–2026 |
| ncaam | Men's College Basketball | 2001–2026 |
| nba | NBA | 1990–2026 |
| nhl | NHL | 1990–2026 (no 2004) |
GET/api/v1/leagues/{league}
{ "data": { "id": "nhl", "name": "NHL", "recordsIncludeTies": true,
"seasons": { "first": 1990, "last": 2026, "default": 2026, "skipped": [2004] } } }
A season is identified by the year it starts in.
6. Ratings
GET/api/v1/leagues/{league}/seasons/{season}/ratings
Every team's current rating, ranks and projection, ordered by this week's rank — the data behind the ratings pages.
| Field | Meaning |
|---|---|
| teamId, team, confDiv | Team id (use it with the games endpoint), name, and conference/division |
| rank.thisWeek / lastWeek / twoWeeksAgo / threeWeeksAgo | Overall rank now and in the previous three weeks (null before the season started) |
| rank.ratingImproved | The rating went up since last week |
| record | Wins, losses (and ties) |
| rating | Overall team rating (higher is better) |
| strengthOfSchedule.value / rank | Strength of schedule and its rank (1 = toughest) |
| allMargin.rating / rank | Rating counting every point of margin |
| noMargin.rating / rank | Rating from wins and losses only |
| vsTopTeams[] | Record against teams currently in the top N (top = N; pro leagues 5/10/15, college 10/25/100) |
| projection.text / wins / losses / ties | Most likely final record |
| projection.chanceWithinOneGame | Chance the final record lands within one game of the projection (while games remain) |
| projection.strengthOfScheduleRank, gamesRemaining, final | Projected full-season SOS rank, games left, and whether the season is complete |
| extra | Any additional values we add in the future, before they get a documented name |
7. Projected standings
GET/api/v1/leagues/{league}/seasons/{season}/standings
Projected final standings, grouped by division (pro leagues) or conference (college), in projected finishing order.
{ "data": { "groupType": "division", "groups": [
{ "name": "AFC East", "teams": [
{ "rank": 5, "teamId": null, "team": "…", "projected": { "wins": 11, "losses": 6, "ties": 0 }, "gamesRemaining": 11 } ] } ] } }
College groups show projectedOverall and projectedConference records and conferenceGamesRemaining.
8. Teams
GET/api/v1/leagues/{league}/seasons/{season}/teams
A light list of teamId, team, confDiv and current rank — handy for looking up team ids.
9. Team games & projections
GET/api/v1/leagues/{league}/seasons/{season}/teams/{teamId}/games
One team's full schedule with results, expectations and the odds of every possible final record.
| Field | Meaning |
|---|---|
| team | Name, current rank, record and most likely final record |
| games[].date, status | Game date; final or scheduled |
| games[].away / home | teamId, team, current rank, record, score and (football) qb1Out — the starting quarterback is out |
| games[].neutralSite, playoff | Played at a neutral site; playoff game |
| games[].outcome | For this team: W, L or T (final games) |
| games[].projectedOutcome | Projected W/L for games not yet played |
| games[].expected.favorite, spread, text | Projected favorite and expected margin, e.g. "Houston Texans (-2.27)" |
| games[].expected.favoriteWinProbability | The favorite's win probability as shown on the site |
| games[].expected.teamWinProbability, teamFavored | This team's win probability, and whether it's the favorite |
| games[].deviation | Actual result minus the expected margin (final games) |
| projection.mostLikelyRecord, gamesRemaining, chanceToWinOut | Most likely final record; games left; chance of winning all of them |
| projection.distribution[] | Every possible final record with its probability (and newTies for the NFL) |
10. Bulk export data license
GET/api/v1/leagues/{league}/seasons/{season}/export
Everything for one league-season in a single response: ratings, standings and every team's games and projections. Available on the data-license plan (export_not_included otherwise). AI-training rights and historical bulk files are arranged by contract — contact us.
11. Your usage
GET/api/v1/usage
Your plan, limits, the key you called with and requests per day for the last 30 days. Doesn't count toward your daily quota (it does count toward the per-minute limit). GET /api/v1/status (no key needed) reports whether the API is up.
12. Errors
Errors use normal HTTP status codes and a consistent body:
{ "error": { "status": 429, "code": "rate_limited",
"message": "Too many requests: your plan allows 30 per minute.",
"docs": "https://www.statsorb.com/developers/docs#errors" },
"requestId": "req_7hQ2…" }
| Status | code | What to do |
|---|---|---|
| 400 | key_in_query | Send the key in a header, not the URL |
| 400 | invalid_season · invalid_team | Check the season range (see leagues) or the team id |
| 401 | missing_api_key · invalid_api_key | Add your key / check it for typos |
| 401 | key_expired · key_revoked | Renew the key or create a new one in your account |
| 402 | payment_required | A subscription payment failed — update your card |
| 403 | key_suspended · account_pending · account_inactive | See the message; contact us if unexpected |
| 403 | ip_not_allowed | The request came from an IP outside the key's allowlist |
| 403 | export_not_included | Bulk export needs a data license |
| 404 | unknown_league · season_not_played · team_not_found · no_data · not_found | Check the address and ids |
| 405 | method_not_allowed | The API is read-only — use GET |
| 429 | rate_limited · daily_limit_reached · overage_cap_reached · too_many_failed_attempts | Wait Retry-After seconds; consider a bigger plan or raising your overage cap |
| 503 | upstream_unavailable · api_disabled | Temporary — retry with backoff (e.g. 1 min, 2 min, 4 min) |
13. Managing keys
- IP allowlist (recommended): limit a key to specific IP addresses or ranges (e.g.
203.0.113.7, 198.51.100.0/24). Requests from anywhere else getip_not_allowed. - Rotation with overlap: “Rotate” creates a replacement key; the old one keeps working for 7 days so you can switch without downtime, then stops.
- Expiry & renewal: keys last 365 days. We e-mail you 14 and 3 days before expiry; “Renew” extends a key without changing it.
- Automatic suspension: to protect you and the service, a key is suspended automatically if it's used from more than 25 different IP addresses in an hour (a sign it's been shared or leaked), keeps sending requests after hitting its rate limit (600 refused requests in an hour), or gets 100 requests from outside its IP allowlist in an hour. We e-mail you; it's reinstated automatically after 24 hours.
- Revoke a key immediately if it may have leaked, and create a new one.
14. Attribution & rules (summary)
- Free keys: non-commercial use only, and you must credit “Data: Stats Orb (statsorb.com)” wherever the data appears (with a link to statsorb.com where possible).
- No AI training on any plan except under a data license.
- Don't share keys, resell or redistribute the raw data in bulk, or try to get around limits (for example with several free accounts).
- Scraping the website instead of using the API isn't allowed — automated requests to the ratings pages are redirected here.
The full rules are in the API terms.
15. Versioning & support
We may add new endpoints and fields to v1 at any time, so ignore fields you don't recognize. Anything that would break existing code will come in a new version (/api/v2), with at least 90 days' notice by e-mail before v1 changes. Questions or problems: contact us and include the X-Request-Id.