Score a match from your system
This guide connects a scoring system to LIGR for one football match. Your system creates the match, posts a fact for every event, and LIGR computes the score, the clock, the statistics and the graphics from those facts. It needs a write key and about half an hour.
The endpoints are under Matches and Facts in the REST reference.
-
Find the ids you need.
A match belongs to a competition, takes place at a venue, and has two teams. Read them once and keep the ids.
Terminal curl 'https://api.ligr.live/rest/v1/competitions' -H "Authorization: Bearer $LIGR_API_KEY"curl 'https://api.ligr.live/rest/v1/competitions/2311/teams' -H "Authorization: Bearer $LIGR_API_KEY"curl 'https://api.ligr.live/rest/v1/venues' -H "Authorization: Bearer $LIGR_API_KEY" -
Create the match.
Terminal curl -X POST 'https://api.ligr.live/rest/v2/matches' \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{"name": "Sydney FC v Melbourne City","competitionId": 2311,"venueId": 764,"date": "2026-09-12T09:30:00.000Z","competitorsType": "teams","competitors": [{ "entityId": 1, "entityType": "team", "meta": { "isHome": true } },{ "entityId": 2, "entityType": "team", "meta": { "isHome": false } }]}'201 Created { "id": 1188213, "overlays": [], "streams": [] }competitorsTypeis the match format:singles,doubles,teamsorgroup.entityTypeis the kind of each row incompetitors:teamorplayer. For example, a doubles match hascompetitorsType: doublesand fourentityType: playerrows. A group match also hasplayerrows.Keep the
id. To add an overlay or a stream to the match, usePOST /v1/overlaysorPOST /v1/streams. -
Set the lineup.
Send the starting players and the substitutes of each team. The graphics use the lineup to show the teams, list the scorers and fill the player variables. Without a lineup, these stay empty.
Each lineup player must be on the roster of the team. Read the roster with
GET /v1/teams/{teamId}/players. If a player is not on the roster, add the player first. This call needs theteams:writescope.Terminal curl -X POST 'https://api.ligr.live/rest/v1/teams/1/players' \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{ "firstName": "Adam", "lastName": "Le Fondre", "number": "9", "position": "Forward", "positionType": "FW" }'201 Created { "id": 501, "firstName": "Adam", "lastName": "Le Fondre", "gfxFirstName": null, "gfxLastName": null, "position": "Forward", "positionType": "FW", "number": "9", "captain": false, "starter": false, "bench": false }Keep the
id. It is theplayerIdin the lineup. Do not use a player of another team as a substitute for a missing player. Add the missing player to the roster.If a name is too long for the graphics, set shorter graphics names on the roster entry. Use
PATCH /v1/teams/{teamId}/players/{playerId}with theteams:writescope. The same call changes the squad number, the position and the default starter flags.Terminal curl -X PATCH 'https://api.ligr.live/rest/v1/teams/1/players/501' \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{ "gfxFirstName": "Bul", "gfxLastName": "Juach" }'If a data provider supplies the player, the next sync replaces the names, the number and the position. The sync does not change
gfxFirstNameandgfxLastName.Then send the lineup.
Terminal curl -X PUT 'https://api.ligr.live/rest/v2/matches/1188213/lineup' \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{"teams": [{"teamId": 1,"starting": [{ "playerId": 501, "captain": true }, { "playerId": 503, "number": "14" }],"bench": [{ "playerId": 502 }]},{ "teamId": 2, "starting": [{ "playerId": 617 }], "bench": [] }]}'200 OK {"lineup": [{ "id": 501, "teamId": 1, "firstName": "Adam", "lastName": "Le Fondre", "number": "9", "position": "Forward", "positionType": "FW", "starter": true, "captain": true, "formationNumber": null, "lineupPosition": null }],"count": 4}The example answer shows one of the four players.
A field you leave out, such as
number, takes the roster value. The call replaces the full lineup of each team you send. LIGR removes a player you leave out. A team you leave out keeps its lineup. If you send the same body again, nothing changes. Open overlays show the new lineup immediately. -
Start the first half.
A football match has these periods. Period
0exists before kick-off.periodNumberPeriod 0Pre-match 1First half 2Half time 3Second half 4Full time Post a
PERIOD_STARTEDfact with the number of the period that starts. The match goes live and the clock starts.Terminal curl -X POST 'https://api.ligr.live/rest/v2/matches/1188213/facts' \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{ "name": "PERIOD_STARTED", "periodNumber": 1 }'201 Created { "id": 56148731, "matchId": 1188213, "name": "PERIOD_STARTED", "periodNumber": 1, "createdAt": "2026-09-12T09:30:02.114Z" }Periods start in order. A
PERIOD_STARTEDfor a period that is not the next one returns 400. -
Post the events.
Every event is one fact.
nameis the event,periodNumberis the period it happened in,teamIdandplayerIdname who did it, anddatacarries the sport-specific detail.A goal curl -X POST 'https://api.ligr.live/rest/v2/matches/1188213/facts' \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{ "name": "GOAL", "periodNumber": 1, "teamId": 1, "playerId": 501, "data": { "shotOnTarget": true } }'201 Created { "id": 56148902, "matchId": 1188213, "name": "GOAL", "periodNumber": 1, "createdAt": "2026-09-12T09:52:17.640Z" }Keep the
idof every fact you post. You need it to correct or delete the fact later.A yellow card curl -X POST 'https://api.ligr.live/rest/v2/matches/1188213/facts' \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{ "name": "YELLOW_CARD", "periodNumber": 1, "teamId": 2, "playerId": 617 }'datedefaults to the time LIGR receives the fact. Send it when your system timestamps the event, so the match minute is right. LIGR derives the minute fromdateand the period start. -
End the half, start the second.
Terminal curl -X POST 'https://api.ligr.live/rest/v2/matches/1188213/facts' \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{ "name": "PERIOD_FINISHED", "periodNumber": 1 }'curl -X POST 'https://api.ligr.live/rest/v2/matches/1188213/facts' \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{ "name": "PERIOD_STARTED", "periodNumber": 3 }'PERIOD_FINISHEDtakes the number of the period that ends. It stops the clock and moves the match into half time, period2.PERIOD_STARTEDwith3starts the second half. -
Correct a mistake.
Update a fact to change who scored or when. Delete a fact that never happened. LIGR recomputes the score and the statistics. Use the
idfrom the answer of step 5, here the goal.The goal was scored by another player curl -X POST 'https://api.ligr.live/rest/v2/matches/1188213/facts/56148902' \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{ "playerId": 502 }'The goal was disallowed curl -X DELETE 'https://api.ligr.live/rest/v2/matches/1188213/facts/56148902' \-H "Authorization: Bearer $LIGR_API_KEY"An update changes only the fields you send. A period fact can be deleted only while it is the most recent one.
-
Close the match.
Post
PERIOD_FINISHEDfor the last period. The match moves to full time. To end a match early, callPOST /v2/matches/{matchId}/finish. To cancel or abandon it, call…/cancelor…/abandon.Terminal curl -X POST 'https://api.ligr.live/rest/v2/matches/1188213/facts' \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{ "name": "PERIOD_FINISHED", "periodNumber": 3 }' -
Read back the result.
Terminal curl 'https://api.ligr.live/rest/v2/matches/1188213/summary' \-H "Authorization: Bearer $LIGR_API_KEY"The summary is the score, the periods, the clock and the statistics that LIGR folded from your facts. Compare it to your own state after every match.
Fact names by sport
Section titled “Fact names by sport”Pick your sport. The tabs stay on your choice across these docs.
name | Event | Who | data |
|---|---|---|---|
PERIOD_STARTED | A period starts | — | — |
PERIOD_FINISHED | A period ends | — | — |
GOAL | A goal | teamId, playerId | { "shotOnTarget": true } |
OWN_GOAL | An own goal. Counts for the other team | teamId, playerId | — |
PENALTY_SCORED | A penalty scored | teamId, playerId | — |
PENALTY_MISS | A penalty missed | teamId, playerId | { "shotOnTarget": false } |
YELLOW_CARD | A yellow card | teamId, playerId | — |
SECOND_YELLOW_CARD | A second yellow card | teamId, playerId | — |
RED_CARD | A red card | teamId, playerId | — |
SUBSTITUTION | A substitution. Counts in substitutions | teamId, playerId | Optional { "off": 501, "on": 502 }, player ids. Swaps the players in the lineup |
PARTIAL_SUBSTITUTION | Swaps two players in the lineup. Does not count in substitutions | teamId | { "off": 501, "on": 502 }, player ids |
CORNER_WON | A corner | teamId | — |
OFFSIDE | An offside | teamId, playerId | — |
FREE_KICK_CONCEDED | A foul that gives a free kick. Counts in fouls | teamId, playerId | — |
PENALTY_CONCEDED | A foul that gives a penalty. Counts in fouls | teamId, playerId | — |
FOUL | A foul. Does not change fouls: send FREE_KICK_CONCEDED or PENALTY_CONCEDED | teamId, playerId | — |
SAVE | A save | teamId, playerId | — |
SHOT_ON_TARGET, SHOT_OFF_TARGET, SHOT_BLOCKED | A shot | teamId, playerId | — |
CLOCK_STOPPED, CLOCK_STARTED | Stop or start the clock inside a period | — | — |
SHOOTOUT_GOAL, SHOOTOUT_MISS | A penalty shootout attempt | teamId, playerId | — |
teamId is the id of the team from the competition. playerId is the id of the player in that
team. See Teams and Players.
fouls is the sum of free kicks conceded and penalties conceded.
Possession, passes, tackles and other Opta-only statistics come only from a data provider feed.
No fact sets them.
The endpoint does not check name against this list. A name LIGR does not know is stored and
drives nothing: no score, no statistic, no graphic. Check your spelling against the table.
Coming soon. The Tennis fact name reference is not written yet.
Until then, ask your LIGR contact for the fact names of Tennis. A name LIGR does not know is stored and drives nothing.
Coming soon. The Basketball fact name reference is not written yet.
Until then, ask your LIGR contact for the fact names of Basketball. A name LIGR does not know is stored and drives nothing.
Coming soon. The Australian Rules fact name reference is not written yet.
Until then, ask your LIGR contact for the fact names of Australian Rules. A name LIGR does not know is stored and drives nothing.
Coming soon. The Rugby League fact name reference is not written yet.
Until then, ask your LIGR contact for the fact names of Rugby League. A name LIGR does not know is stored and drives nothing.
Coming soon. The Rugby Union fact name reference is not written yet.
Until then, ask your LIGR contact for the fact names of Rugby Union. A name LIGR does not know is stored and drives nothing.
Coming soon. The Cricket fact name reference is not written yet.
Until then, ask your LIGR contact for the fact names of Cricket. A name LIGR does not know is stored and drives nothing.
Coming soon. The Netball fact name reference is not written yet.
Until then, ask your LIGR contact for the fact names of Netball. A name LIGR does not know is stored and drives nothing.
Coming soon. The fact name reference for these sports is not written yet. The sport key is in brackets.
- Baseball (
baseball) - Field hockey (
fieldHockey) - Futsal (
futsal) - American Football (
gridiron) - Handball (
handBall) - Ice hockey (
iceHockey) - Lacrosse (
lacrosse) - Rugby Sevens (
rugbySevens) - Touch football (
touchFootball) - Volleyball (
volleyball) - Water polo (
waterPolo)
Ask your LIGR contact for the fact names of these sports.
Errors
Section titled “Errors”| Status | Message | Cause |
|---|---|---|
| 400 | Could not find period 'N' | periodNumber is not a period of this match |
| 400 | Period has already started | A second PERIOD_STARTED for the current period |
| 400 | Could not start period too far in the future. Next period is 'N' | A PERIOD_STARTED that skips a period |
| 400 | Invalid lineup | A lineup player is not on the team roster, or a team does not play in the match. details names each bad value |
| 403 | — | A read key. Use a write key |
| 404 | — | The match belongs to another organization |
| 404 | No fact found with id 'N' | The fact does not belong to the match in the path |
Keep LIGR in sync
Section titled “Keep LIGR in sync”- Post every fact once. LIGR does not deduplicate facts.
- Post facts in the order they happened. The summary is folded in fact order.
- Subscribe a webhook to
factandsummaryevents to confirm what LIGR computed. See Receive webhooks.