For football data systems, one of the most interesting pre-match events isn't a goal or a red card.
It's the moment a predicted lineup becomes the official XI.
That transition happens around the final hour before kickoff in many matches, and it can invalidate assumptions made by betting models, fantasy systems, alerts, and match-intelligence products.
I’ve been thinking about how to model this as an event-driven system rather than simply polling an endpoint and overwriting the latest lineup.
1. Treat the lineup as a state machine
A simple model looks like:
PREDICTED XI ↓ OFFICIAL XI ↓ KICK-OFF ↓ IN-PLAY SUBSTITUTIONS
The important event isn't:
"API returned JSON"
It's:
PREDICTED → OFFICIAL
That should generate a domain event:
{ "eventType": "LINEUP_CONFIRMED", "matchId": "386285032", "timestamp": "...", "homeChanges": 2, "awayChanges": 1 }
The downstream systems shouldn't need to know how the data was collected.
They only need to know that the lineup state changed.
2. The raw API already gives us the pieces
A real lineup response contains:
homeLineup awayLineup homeBackup awayBackup
and each player has fields such as:
playerId name number position isCaptain
For example:
{ "playerId": "165681", "name": "Nicolas Gil", "number": 5, "position": 0, "isCaptain": true }
The interesting part for an event system is that playerId gives us a stable identity.
I would use IDs for state comparison rather than player names.
Something as simple as:
added = current_xi - previous_xi removed = previous_xi - current_xi
already gives us the basic lineup mutation.
3. But "player changed" is not enough
Suppose two players move between the predicted and confirmed XI.
Case A:
reserve defender ↓ starting defender
Case B:
starting striker ↓ bench starting captain ↓ bench
Both might produce:
2 XI changes
But the football context is obviously different.
So instead of one lineup_changed = true flag, I'd generate multiple signals:
XI player changes Captain changes Formation changes Bench changes Player importance
Then an application can construct its own:
Lineup Impact Score
The important distinction is that the API supplies the raw state.
The scoring model belongs to the application.
4. One API edge case I wouldn't ignore
Here's something I noticed in a real response:
"homeFormation": "", "awayFormation": ""
and the players contain:
"position": 0
It would be tempting to interpret position = 0 as a specific position.
That would be a mistake.
According to the Lineups documentation, when formation data isn't available, player positions return 0. Substitute players also return 0 regardless of formation.
So:
position = 0
doesn't necessarily mean:
Goalkeeper
It can simply mean:
Formation unavailable
This is a good example of why API fields need to be interpreted in context rather than hard-coded into business logic.
5. Don't overwrite the previous lineup
This is probably the biggest architectural decision.
I wouldn't do:
current_lineup = new_response
and throw away the previous state.
I'd store snapshots:
match 386285032 Snapshot #1 Predicted XI ↓ Snapshot #2 Official XI
Then derive:
LINEUP_SHOCK
from the difference.
That gives you historical information such as:
Which players changed? Was the predicted XI accurate? Which clubs frequently change their predicted lineups? How often does the captain change? How large are lineup changes before kickoff?
Those can later become useful model features.
6. The event bus sits between data and applications
A possible architecture:
Lineup API │ ▼ Lineup Poller │ ▼ State Store │ ▼ Diff Engine │ ▼ Event Bus │ ┌─────────┼─────────┐ ▼ ▼ ▼ Odds Alerts Analytics Engine Bot Engine │ │ ▼ ▼ Market Telegram Signals / Discord
This separation is useful because the lineup provider doesn't need to know what consumers are doing with the event.
A betting model can consume it.
A fantasy application can consume it.
An alert bot can consume it.
A frontend can consume it.
7. The latency problem is more subtle than it looks
I wouldn't define the system simply as:
JSON processing = 20 ms
The real pipeline is closer to:
Official lineup published ↓ API availability ↓ Polling ↓ Change detection ↓ Event bus ↓ Consumer ↓ User notification
So the useful latency is:
T(user receives event) - T(official lineup becomes available)
not merely how quickly your code parses JSON.
This distinction becomes important when the downstream application is reacting to market information.
8. The iSports endpoint makes an interesting state-transition model possible
The current Lineups documentation supports isPreview=true, which returns the latest available lineup — predicted or official. The response automatically switches from Predicted to Official once the official lineup is released, typically around 60 minutes before kickoff. The endpoint returns the starting XI, substitutes, formation, player IDs, shirt numbers, positions and captain information. The documented call limit is 60 seconds per call, with 90 seconds recommended.
That means the application doesn't necessarily need an aggressive high-frequency poller.
The more interesting problem is how to turn:
latest snapshot
into:
reliable state transition
9. Then comes the market question
Once we have:
T-90m Predicted XI ↓ T-60m Official XI ↓ LINEUP_SHOCK
we can align market data around that event:
T-10m T-5m T0 ← LINEUP_SHOCK T+5m T+10m
Then measure:
odds change implied probability change handicap movement total-line movement market liquidity
I'm deliberately not saying:
lineup change → odds change
because correlation isn't causation.
But this event structure gives us a much cleaner way to investigate the relationship.
10. A minimal event schema
Something like:
{ "eventType": "LINEUP_SHOCK", "matchId": "386285032", "timestamp": "...", "diff": { "homeAdded": [], "homeRemoved": [], "awayAdded": [], "awayRemoved": [], "captainChanged": false, "formationChanged": false } }
From there, you can attach additional application-level features:
player importance historical minutes position/role team dependency odds movement
But the core event stays simple.
The bigger idea
I don't think the hard problem is:
The hard problem is:
A lineup API gives you a snapshot.
A Lineup Shock system gives you the transition between snapshots.
And once you model:
Predicted XI ↓ Confirmed XI ↓ Diff ↓ Event ↓ Consumers
the same event can power odds analysis, alerts, fantasy products, and historical modeling without tightly coupling those systems together.
Don't overwrite the lineup. Model the change.
How are other teams handling this?
Do you store every lineup snapshot, or only the final confirmed XI?
Source: r/sportsdataapi · by /u/iSportsAPI