REST API reference

Every supported HTTP endpoint Goalfeed exposes with --web, on --web-port (default 8080).

Minimal working example

bash
curl -s http://localhost:8080/api/games | python3 -m json.tool

Every JSON endpoint wraps its payload in the same envelope:

go
type ApiResponse struct {
	Success bool        `json:"success"`
	Data    interface{} `json:"data"`
	Message string      `json:"message,omitempty"`
}
Worth knowing

There is no authentication on any endpoint below, and CORS allows every origin. This is by design for a home-network tool — see Security before putting this API anywhere reachable from the internet.

Games

GET /api/games

All currently active games across every monitored team and league.

ParamTypeDefaultRequiredNotes
none
bash
curl -s http://localhost:8080/api/games

Response: data is an array of models.Game{ leagueId, gameCode, currentState: { home, away, period, clock, status, venue, weather }, statistics, ... }. NFL games get on-the-fly enrichment of missing down/distance/possession from a fresh ESPN call. Errors: 500 on an internal failure.

GET /api/games/history

Games (any registered league, including IIHF) on a given date, filtered to your monitored teams. Not listed in the generated Swagger spec (docs/swagger.yaml) even though it's real and working — a documentation gap in the generated spec, not a hint that the endpoint is unsupported.

ParamTypeDefaultRequiredNotes
datestringyes YYYY-MM-DD. Missing or malformed → 400
bash
curl -s "http://localhost:8080/api/games/history?date=2026-01-15"

Response: data is an array of models.Game, restricted to leagues where watch.<league> has at least one team configured — a league with no watched teams is skipped entirely, not returned empty. Errors: 400 (missing/invalid date).

GET /api/upcoming

Upcoming games (next 7 days) for monitored teams across every registered league, including IIHF.

bash
curl -s http://localhost:8080/api/upcoming

Response: data is an array of models.Game. Errors: 500.

POST /api/clear

Clears every tracked game from the in-memory store. Safe to call any time — the store is ephemeral, and the next poll cycle rebuilds it.

bash
curl -s -X POST http://localhost:8080/api/clear

Response: { "success": true }. Errors: 500.

POST /api/refresh — documented, not functional

Do not rely on this

The handler is an explicit placeholder left over from a refactor. It logs "Refresh active games - placeholder implementation" and returns HTTP 200 unconditionally — it does not refresh anything. Games refresh on their own every minute regardless of whether you call this.

Events & logs

GET /api/events

Recent goal/score events, most recent first.

ParamTypeDefaultRequiredNotes
leagueIdintegerno see leagues reference for IDs
teamstringnoteam code
sincestringnoRFC3339 timestamp
limitinteger50nomax rows returned
bash
curl -s "http://localhost:8080/api/events?leagueId=1&team=WPG&limit=10"

Response: data is an array of models.Event objects. Remember: type and description are empty strings for NHL/MLB/NFL/CFL events — only Olympic hockey populates them (see WebSocket reference). Errors: 400 (bad since/limit), 500.

GET /api/logs

The JSONL application log — every event delivery attempt (success or failure) and state change, queryable. This is where the errors on the troubleshooting page actually show up.

ParamTypeDefaultRequiredNotes
leagueIdintegerno
teamstringno
sincestringnoRFC3339
limitinteger0 (all)no
bash
curl -s "http://localhost:8080/api/logs?limit=5"

Response (real, captured):

json
{
  "id": "1787264741605724000-TEST-",
  "type": "event",
  "teamCode": "TEST",
  "target": "ha:event:goal",
  "success": false,
  "error": "refusing to send Home Assistant request: home assistant url is empty",
  "timestamp": "2026-08-20T17:25:41.605724-05:00"
}

Errors: 400, 500.

Teams & leagues

GET /api/teams

ParamTypeDefaultRequiredNotes
leagueIdintegeryes 1=NHL, 2=MLB, 5=CFL, 6=NFL (full table: leagues reference)
bash
curl -s "http://localhost:8080/api/teams?leagueId=1"

Response: data is an array of team objects (teamId, teamCode, teamName, leagueId, logoUrl). Errors: 400 (missing/invalid leagueId), 500.

GET /api/leagues

Current per-league watch configuration.

bash
curl -s http://localhost:8080/api/leagues

Errors: 500.

POST /api/leagues

Replaces the watched-team list for one league.

Body fieldTypeRequired
leagueIdintegeryes
teamsarray of stringyes
bash
curl -s -X POST http://localhost:8080/api/leagues \
  -H "Content-Type: application/json" \
  -d '{"leagueId": 1, "teams": ["WPG", "TOR"]}'

Errors: 400 (bad body), 500.

Home Assistant

GET /api/homeassistant/status

Whether Goalfeed can currently reach Home Assistant, and via which source.

bash
curl -s http://localhost:8080/api/homeassistant/status

Response: data is { connected, source, message, url, tokenSet } — note tokenSet is a boolean, the access token itself is never echoed here. Errors: 500.

GET /api/homeassistant/config

Response: data.configured is { url, tokenSet } — again, boolean only; the token value is never returned by this endpoint. Errors: 500.

POST /api/homeassistant/config

Body fieldTypeDefaultRequiredNotes
urlstring""no validated — a public/remote URL is rejected unless home_assistant.allow_remote_url is true (see Security)
accessTokenstring""no
clearTokenboolfalseno if true, clears the stored token regardless of accessToken
bash
curl -s -X POST http://localhost:8080/api/homeassistant/config \
  -H "Content-Type: application/json" \
  -d '{"url": "http://homeassistant.local:8123", "accessToken": "your-long-lived-token"}'

Response message differs depending on web.allow_config_writes: by default, "Configuration updated for this session only (not persisted to disk). Set web.allow_config_writes: true in config.yaml to allow this API to write config.yaml." — the change applies in-memory immediately but reverts on restart unless that key is set. Errors: 400 (invalid JSON, or a URL rejected by the remote-address check).

Excluded from this reference

POST /api/debug/nfl/add exists in the code and is tagged debug in its own Swagger annotation. It force-adds an NFL game by ESPN event ID for testing and is intentionally left out of this reference — it is not a supported part of the public API surface.

Next

Real-time push instead of polling this API — WebSocket reference. Threat model for running this unauthenticated API at all — Security.