Game On SignAPI v1 HelpDisplay setup →

GameOnSign API v1

Free, read-only JSON for scoreboards, signs and "game on" relays: NHL, NFL, NBA and MLB, all 124 teams. Built for microcontrollers (ESP32, ESP8266, Pico W) as much as for browsers.

Base URL: https://gameonsign.com/api/v1/ (plain http:// works too, so small boards can skip TLS)

Endpoint Use it for Typical size Suggested polling
live.php relays, LED signs, score bugs ~350 B every poll seconds (15 s near/during games, 60–300 s otherwise)
next.php clock / next-game card, countdown ~750 B every poll seconds
schedule.php upcoming games list 1–4 KB hourly
teams.php team pickers, names, colors ~3 KB once (cache it)
multi.php several teams in one call (grid displays) ~0.75 KB per team every poll seconds

Conventions (stable within v1)

States

state meaning
pre scheduled / pre-game
live in progress (includes intermissions, halftime, rain delay, overtime, shootout)
final finished. Shown for a few hours after the game ends, unless the next game starts within an hour.
postponed postponed or suspended
none no upcoming game known (season over, or the team was eliminated)

game_on, the sign signal

game_on is true from the scheduled start time (or as soon as the league marks the game live) until the league confirms a final. It stays true through intermissions, halftime, delays and overtime. If the league feed still says "pre" after the scheduled start (late start), game_on is already true. It is never true for a tbd start time unless the game is actually live.

sign object

field meaning
on same as game_on
off_confirmed true once the league reports the game final
flash_at [start-300, start-120]: flash the sign at T-5 min and T-2 min. Empty if the time is TBD.
on_at scheduled start (epoch), 0 if TBD
est_off on_at + typical game length (NHL 2h30, NFL 3h15, NBA 2h25, MLB 2h50). Offline fallback only.

Recommended relay logic, used by the example sketch:

online : relay = game_on, or blinking during [flash_at[i], flash_at[i]+20s)
offline: relay = (on_at <= now < est_off) and not off_confirmed, plus the same flash bursts

poll, the server's polling hint (seconds)

15 while a game is on or starts within 15 min, 60 within 2 h, 300 otherwise, 3600 with no game. Please honor it, because it keeps the service free.


GET /api/v1/live.php?league={league}&team={abbr}

The smallest document: current game state for relays and signs.

{
 "v": 1,
 "league": "nhl",
 "team": "TBL",
 "state": "live",
 "game_on": true,
 "id": "2026020060",
 "start": 1791500400,
 "tbd": false,
 "home": true,
 "opp": "MIN",
 "matchup": "MIN@TBL",
 "score": {
  "team": 3,
  "opp": 2
 },
 "period": "3rd",
 "clock": "09:51",
 "inter": false,
 "detail": "3rd 09:51",
 "sign": {
  "on": true,
  "off_confirmed": false,
  "flash_at": [
   1791500100,
   1791500280
  ],
  "on_at": 1791500400,
  "est_off": 1791509400
 },
 "poll": 15,
 "updated": 1791508466,
 "stale": false
}
field meaning
id league game id (string)
start, tbd start time (epoch). If tbd, the time is not set yet and start is 8 PM ET on day.
home true if team is the home team
matchup always AWAY@HOME (e.g. MIN@TBL), independent of which team you asked for. Displays should put the away team on the left and the home team on the right.
opp opponent abbreviation, or TBD (e.g. playoff opponent not decided)
score {"team":n,"opp":n} when live/final, else null
period 1st 2nd 3rd OT 2OT SO (NHL) · Q1–Q4 OT Half (NFL/NBA) · Top 7th Bottom 7th Mid 7th End 7th (MLB)
clock game clock 12:34 (NHL/NFL/NBA). For MLB, outs: 2 out.
inter true during an intermission, halftime, end of a quarter, or between half-innings
detail human status: 2nd Intermission, Halftime, Final/OT, Rain Delay, Pre-Game …

GET /api/v1/next.php?league={league}&team={abbr}

Everything in live.php, plus names, venue and neighbors: the current game if live (or just finished), otherwise the next one.

{
 "v": 1,
 "league": "nfl",
 "team": "TB",
 "state": "live",
 "game_on": true,
 "id": "401872980",
 "start": 1791504900,
 "tbd": false,
 "day": "2026-10-08",
 "home": false,
 "opp": "DAL",
 "matchup": "TB@DAL",
 "opp_name": "Dallas Cowboys",
 "score": {
  "team": 7,
  "opp": 7
 },
 "venue": "AT&T Stadium",
 "city": "Arlington",
 "season": "reg",
 "period": "Q2",
 "clock": "5:30",
 "inter": false,
 "detail": "5:30 - 2nd",
 "sign": {
  "on": true,
  "off_confirmed": false,
  "flash_at": [
   1791504600,
   1791504780
  ],
  "on_at": 1791504900,
  "est_off": 1791516600
 },
 "next": {
  "id": "401873002",
  "start": 1792342800,
  "tbd": false,
  "day": "2026-10-18",
  "home": true,
  "opp": "PIT",
  "matchup": "PIT@TB",
  "state": "pre"
 },
 "last": {
  "id": "401872968",
  "start": 1791133200,
  "tbd": false,
  "day": "2026-10-04",
  "home": true,
  "opp": "GB",
  "matchup": "GB@TB",
  "state": "final",
  "score": {
   "team": 14,
   "opp": 17
  },
  "result": "L"
 },
 "poll": 15,
 "updated": 1791508467,
 "stale": false
}

Extra fields: opp_name, venue, city, day, season (pre, reg or post), note (playoff label, e.g. ALCS Game 1), if_nec (if-necessary playoff game), result (W, L or T when final), and next/last, compact summaries of the following game and the most recent final.

Countdown captions used by the web display: NHL "Countdown to puck drop", NFL "Countdown to kickoff", NBA "Countdown to tip-off", MLB "Countdown to first pitch".

GET /api/v1/schedule.php?league={league}&team={abbr}&n=16

Upcoming games, soonest first (including a game in progress).

param default
n 16 1–40 games
venue 1 0 drops venue/city (smaller)
past 0 also include the last 1–10 finals (with score, result)
all 0 1 returns the whole season (preseason, regular season, playoffs; past results and upcoming) plus record (regular season, NHL as W-L-OTL). Each game adds season (pre/reg/post), det, note, on for a game in progress. Not size-trimmed (MLB is ~45 KB), so it is meant for browsers; it powers schedule.html. Other params are ignored.

Responses are kept under 4 KB: if a request would be larger, venue text is dropped first, then games, and "trimmed":true is set.

{
 "v": 1,
 "league": "nfl",
 "team": "TB",
 "count": 3,
 "games": [
  {
   "id": "401872980",
   "start": 1791504900,
   "tbd": false,
   "day": "2026-10-08",
   "home": false,
   "opp": "DAL",
   "matchup": "TB@DAL",
   "state": "live",
   "score": {
    "team": 7,
    "opp": 7
   },
   "venue": "AT&T Stadium",
   "city": "Arlington"
  },
  {
   "id": "401873002",
   "start": 1792342800,
   "tbd": false,
   "day": "2026-10-18",
   "home": true,
   "opp": "PIT",
   "matchup": "PIT@TB",
   "state": "pre",
   "venue": "Raymond James Stadium",
   "city": "Tampa"
  },
  {
   "id": "401873018",
   "start": 1792947600,
   "tbd": false,
   "day": "2026-10-25",
   "home": false,
   "opp": "CAR",
   "matchup": "TB@CAR",
   "state": "pre",
   "venue": "Bank of America Stadium",
   "city": "Charlotte"
  }
 ],
 "updated": 1791508467,
 "stale": false
}

GET /api/v1/teams.php[?league={league}]

Without league: the list of leagues. With league: every team, with ESPN primary/alternate colors (c1, c2, hex without #).

{
 "v": 1,
 "league": "nba",
 "count": 30,
 "teams": [
  {
   "abbr": "ATL",
   "name": "Atlanta Hawks",
   "short": "Hawks",
   "c1": "c8102e",
   "c2": "fdb927"
  },
  {
   "abbr": "BOS",
   "name": "Boston Celtics",
   "short": "Celtics",
   "c1": "008348",
   "c2": "ffffff"
  }
 ],
 "logo": "/assets/logos/nba/{abbr}.png"
}

Logos (PNG, 256×256, transparent): /assets/logos/{league}/{abbr-lowercase}.png, plus -d.png variants for dark backgrounds, e.g. /assets/logos/nhl/tbl-d.png.

GET /api/v1/multi.php?teams=nhl:TBL,nfl:TB,mlb:TB

Up to 12 league:ABBR pairs. Returns {"v":1,"count":n,"items":[ next.php documents ],"poll":min,"updated":...}. Unknown pairs are skipped.

GET /api/v1/scoreboard.php?league=all

Who is playing today, for every league in one request (or league=nhl|nfl|nba|mlb for one). "Today" is the US Eastern date; late games from yesterday's slate are included while they are still live. Cached about 20 s server-side, so poll it at most once a minute (poll is 60).

{"v":1,"day":"2026-10-08","leagues":{"nfl":{"live":1,"games":[{"id":"401872980","a":"TB","h":"DAL","state":"live","on":true,
 "start":1791504900,"tbd":false,"as":7,"hs":7,"det":"2:00 - 2nd"}]}},"poll":60,"updated":1791509360,"stale":false}
field meaning
leagues.{lg}.live number of games with on: true
games[].a / h away / home team abbreviation (always AWAY @ HOME)
games[].state pre, live, final, postponed, cancelled
games[].on same rule as game_on: true from scheduled start until a confirmed final
as / hs, det away / home score and a status line, when available

Live games are listed first. A league whose upstream failed carries "error":true. The response for all four leagues is a few KB; ESP32 devices that follow a single team should keep using live.php (smaller).


ESP32 example (relay sign)

esp32_gameon_relay.ino uses WiFi, HTTPClient and ArduinoJson 7. It polls live.php, honors poll and ETags, flashes the sign at T-5 and T-2, holds it on until a confirmed final, and falls back to on_at/est_off when offline. Core of it:

http.useHTTP10(true);
if (etag.length()) http.addHeader("If-None-Match", etag);
int code = http.GET();                       // 304 = unchanged
JsonDocument doc; deserializeJson(doc, http.getStream());
bool gameOn   = doc["game_on"] | false;
uint32_t f5   = doc["sign"]["flash_at"][0] | 0, f2 = doc["sign"]["flash_at"][1] | 0;
uint32_t poll = doc["poll"] | 60;

Data sources

Data comes from the leagues' public feeds (NHL api-web, MLB Stats API) and ESPN (NFL, NBA). The server caches it (schedules 20–45 min, live scoreboards 15 s during games), so devices never hit the leagues directly. Logos and team names are trademarks of their owners and are shown for identification only.

API terms

Reserved for later