// developers / docs

API documentation

Version 1 · base URL https://www.statsorb.com/api/v1 · JSON over HTTPS · machine-readable spec: openapi.json

1. Quick start

  1. Request a key (free for non-commercial use, or subscribe to a paid plan) and confirm your e-mail address.
  2. Once approved, sign in and create a key. It looks like so_AbCd2345_… and is shown once.
  3. 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 with key_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 include ties only for leagues that have ties (NFL, NHL).
  • Every response carries an X-Request-Id header — 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

PlanPer minutePer day (UTC)Past the daily limit
Free (non-commercial)305,000Refused until 00:00 UTC
Developer120100,000Allowed, billed at $9 per 1,000 requests (up to your spending cap)
Business3001,000,000Allowed, billed at $9 per 1,000 requests (up to your spending cap)
Data license6005,000,000By agreement

The per-minute limit is a rolling 60-second window. Every metered response includes:

X-RateLimit-LimitRequests allowed per minute
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetSeconds until the window fully resets
X-Daily-Limit / X-Daily-RemainingYour daily quota and what's left of it today (UTC)
X-Overage: trueThis request was past your daily limit and counts as billable overage
Retry-AfterOn 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:

idLeagueSeasons
nflNFL1990–2026
ncaafCollege Football1990–2026
ncaamMen's College Basketball2001–2026
nbaNBA1990–2026
nhlNHL1990–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.

FieldMeaning
teamId, team, confDivTeam id (use it with the games endpoint), name, and conference/division
rank.thisWeek / lastWeek / twoWeeksAgo / threeWeeksAgoOverall rank now and in the previous three weeks (null before the season started)
rank.ratingImprovedThe rating went up since last week
recordWins, losses (and ties)
ratingOverall team rating (higher is better)
strengthOfSchedule.value / rankStrength of schedule and its rank (1 = toughest)
allMargin.rating / rankRating counting every point of margin
noMargin.rating / rankRating 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 / tiesMost likely final record
projection.chanceWithinOneGameChance the final record lands within one game of the projection (while games remain)
projection.strengthOfScheduleRank, gamesRemaining, finalProjected full-season SOS rank, games left, and whether the season is complete
extraAny 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.

FieldMeaning
teamName, current rank, record and most likely final record
games[].date, statusGame date; final or scheduled
games[].away / hometeamId, team, current rank, record, score and (football) qb1Out — the starting quarterback is out
games[].neutralSite, playoffPlayed at a neutral site; playoff game
games[].outcomeFor this team: W, L or T (final games)
games[].projectedOutcomeProjected W/L for games not yet played
games[].expected.favorite, spread, textProjected favorite and expected margin, e.g. "Houston Texans (-2.27)"
games[].expected.favoriteWinProbabilityThe favorite's win probability as shown on the site
games[].expected.teamWinProbability, teamFavoredThis team's win probability, and whether it's the favorite
games[].deviationActual result minus the expected margin (final games)
projection.mostLikelyRecord, gamesRemaining, chanceToWinOutMost 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…" }
StatuscodeWhat to do
400key_in_querySend the key in a header, not the URL
400invalid_season · invalid_teamCheck the season range (see leagues) or the team id
401missing_api_key · invalid_api_keyAdd your key / check it for typos
401key_expired · key_revokedRenew the key or create a new one in your account
402payment_requiredA subscription payment failed — update your card
403key_suspended · account_pending · account_inactiveSee the message; contact us if unexpected
403ip_not_allowedThe request came from an IP outside the key's allowlist
403export_not_includedBulk export needs a data license
404unknown_league · season_not_played · team_not_found · no_data · not_foundCheck the address and ids
405method_not_allowedThe API is read-only — use GET
429rate_limited · daily_limit_reached · overage_cap_reached · too_many_failed_attemptsWait Retry-After seconds; consider a bigger plan or raising your overage cap
503upstream_unavailable · api_disabledTemporary — 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 get ip_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.