5.7 KiB
Tournament Transaction Log API
Purpose
Tournament transaction log is owner-readable tournament history.
It records ordered tournament events such as:
- tournament creation
- timer changes
- match state changes
- match score changes
- decider match creation
- playoff start attempts and failures
- playoff start success
- group match auto-starts
Frontend should show this as an append-only history feed for tournament owners.
Read Flow
Transaction log is not embedded in normal tournament payload. Read it from dedicated owner-only endpoint:
GET /tournaments/:id/transaction_log
Auth:
- same Devise token auth headers as other owner APIs
- endpoint requires authenticated tournament owner
Response:
200 OK- JSON array
- ordered by
sequenceascending - no pagination yet
Example:
[
{
"id": 17,
"sequence": 1,
"action": "tournament.created",
"message": "owner created tournament Spring Cup(123)",
"metadata": {
"tournament_id": 123,
"tournament_name": "Spring Cup"
},
"created_at": "2026-04-30T10:12:42Z",
"user": {
"id": 4,
"username": "owner"
}
},
{
"id": 18,
"sequence": 2,
"action": "match.state_changed",
"message": "owner changed match 77 (Team A(10) vs Team B(11)) to running",
"metadata": {
"match_id": 77,
"stage_type": "playoffs",
"old_state": "not_started",
"new_state": "in_progress",
"team_ids": [10, 11],
"teams": [
{ "id": 10, "name": "Team A" },
{ "id": 11, "name": "Team B" }
],
"winner_team_id": null,
"advanced_match_ids": [],
"group_scores": []
},
"created_at": "2026-04-30T10:13:01Z",
"user": {
"id": 4,
"username": "owner"
}
}
]
Field Contract
Each entry has:
id: DB id for entrysequence: per-tournament monotonic order number, starts at1action: machine-readable action keymessage: human-readable owner history textmetadata: structured action-specific datacreated_at: ISO-8601 timestampuser: actor object, ornullfor system actions
user shape:
{
"id": 4,
"username": "owner"
}
Frontend display guidance:
- sort by
sequence, not by timestamp - use
messagefor first UI pass - use
metadatafor links/details/chips - tolerate unknown future
actionvalues - tolerate extra
metadatafields
Current Action Keys
Current action values:
tournament.createdtournament.timer_changedmatch.state_changedmatch_score.updateddecider_match.createdplayoffs.start_failedplayoffs.startedgroup_matches.started
Future backend work may add more action keys.
Unknown actions should still render via message.
Metadata Notes
metadata is stable JSON, but shape depends on action.
Common match metadata includes:
match_idstage_idgroup_idstage_type:groupstageorplayoffsteam_idsteamswinner_team_idadvanced_match_idsgroup_scores
Team object shape:
{
"id": 10,
"name": "Team A"
}
Group score object shape:
{
"team_id": 10,
"team_name": "Team A",
"points": 9,
"matches_played": 3,
"wins": 3,
"draws": 0,
"losses": 0,
"score": 12,
"score_against": 4,
"score_difference": 8,
"position": 1
}
Example Actions
Match state change
{
"action": "match.state_changed",
"message": "owner changed match 77 to finished (Team A(10) wins) - playoffs - Team A(10) advances to match 80",
"metadata": {
"match_id": 77,
"stage_type": "playoffs",
"old_state": "in_progress",
"new_state": "finished",
"winner_team_id": 10,
"advanced_match_ids": [80]
}
}
Match score update
{
"action": "match_score.updated",
"message": "owner changed matchscore 91 from match 77 to 3 (3 points for Team A(10) in match vs Team B(11))",
"metadata": {
"match_id": 77,
"match_score_id": 91,
"team_id": 10,
"changes": {
"points": {
"from": 2,
"to": 3
}
}
}
}
Playoff start failure
{
"action": "playoffs.start_failed",
"message": "owner attempted playoff start, failed because decider missing",
"metadata": {
"stage_id": 12,
"reason": "decider_missing",
"blocking_ties": [
{
"group_id": 5,
"team_ids": [10, 11],
"teams": [
{ "id": 10, "name": "Team A" },
{ "id": 11, "name": "Team B" }
]
}
]
}
}
Playoffs started
{
"action": "playoffs.started",
"message": "owner started playoffs, advancing teams: Team A(10), Team B(11), Team C(12), Team D(13)",
"metadata": {
"stage_id": 12,
"advancing_team_ids": [10, 11, 12, 13],
"advancing_teams": [
{ "id": 10, "name": "Team A" },
{ "id": 11, "name": "Team B" },
{ "id": 12, "name": "Team C" },
{ "id": 13, "name": "Team D" }
]
}
}
Error Behavior
Expected errors:
401 Unauthorized: missing or invalid auth403 Forbidden: authenticated user is not tournament owner404 Not Found: tournament id does not exist
Read-only follower tournaments:
- log read is allowed for owner
- log write is internal only; frontend has no write endpoint
Frontend Implementation Guide
Recommended owner-history flow:
- Load tournament as usual.
- If current user is owner, request
GET /tournaments/:id/transaction_log. - Render entries sorted by
sequence. - Use
messageas main text. - Show
created_atas event time. - Link matches/teams from
metadatawhen ids are present. - Refresh after owner mutations, or poll while history panel is open.
There is no websocket stream for transaction log yet. Existing tournament/match websocket updates still fire for live tournament state.