SERENTECH Live Data API
The SERENTECH Live Data API exposes provisional live MPFL match state from venue edge HTTP pushes, legacy venue edge proxying, or the legacy MPFL Firestore live source. Default push mode is a single-process in-memory cache; it is not restart-safe or a shared multi-venue durability layer. Certified final records come from MPFL event-ledger import and certification.
SDK
Official SERENTECH clients can use the internal TypeScript, Dart, and Kotlin SDKs for Local Edge access plus MPFL cloud read fallback. SDK handoff: SERENTECH SDK docs.
Overlay integration guide
Overlay apps should use local-first discovery, select a matchId, subscribe to SSE, and fall back to polling without clearing the last known score. Full handoff: SERENTECH overlay integration guide.
Local-first discovery: saved URL -> cloud-announced Edge addresses -> serentech.local:8787 -> static local candidates -> real-netmask subnet sweep -> https://api.mpfl.my
Static asset caching
Cache static URLs returned by the API, including crests and other overlay media, by URL. Do not refetch those assets on every state poll or SSE event; only refresh when the URL changes, the HTTP cache expires, or an operator explicitly clears the cache.
Operator event writes
Latency-sensitive operator actions such as clock, cards, fouls, horn, shootout and certification are local Edge writes only. Use POST /matches/:matchId/events on the venue LAN so the event is recorded immediately in the local immutable ledger and projected into state. The MPFL cloud ingest route accepts only Edge ingest keys; provider keys are read-only and cannot push.
Base URL
https://api.mpfl.my
Authentication
Provider endpoints accept Authorization: Bearer <key> or x-api-key: <key>. Keys start with sk_serentech_ and are managed in MPFL Control on https://mpfl.my/admin.
Endpoints
| Endpoint | Purpose |
|---|---|
GET /api/serentech/matches | Published match IDs, fixture context, mapped teams, matchup display data and overlay metadata for approved providers. |
GET https://mpfl.my/api/live/matches/:matchId/state | Current provisional pushed live state for MPFL-owned website surfaces on the main host. |
GET /api/serentech/matches/:matchId/state | Current provisional live state for an approved provider. |
GET /api/serentech/matches/:matchId/stream | Server-Sent Events stream of match state updates. |
POST /api/serentech/matches/:matchId/state | Write-only Edge ingest route for current projected match state. Requires an edge ingest key; provider keys cannot write. |
POST /api/serentech/matches/:matchId/events | Write-only Edge ingest route for asynchronously synced MatchEvent records. Requires an edge ingest key; provider keys cannot write. |
GET /api/serentech/releases/version.json | Public SERENTECH app and runtime release metadata. |
GET /api/serentech/releases/version.xml | Public SERENTECH version XML for update clients. |
GET /api/serentech/releases/download/:filename | Public download for manifest-listed artifacts, including SERENTECH direct APKs used by in-house tablet updates. |
POST /api/admin/serentech/releases/artifacts | MPFL Control admin upload for SERENTECH direct APKs, with Google Play universal APKs still accepted for Play-managed devices. Requires same-origin Firebase admin auth and publishes SHA-256 verified artifact metadata into the release manifest. |
Example
curl -H "Authorization: Bearer sk_serentech_example" https://api.mpfl.my/api/serentech/matches/mpfl-live-001/state
Sample response structures
Provider routes return JSON envelopes. SSE routes emit event: state frames with the latest state as JSON.
Cloud match discovery
{
"source": "fixtures",
"meta": {
"localBaseUrlCandidates": [
"http://serentech.local:8787",
"http://10.1.100.1:8787",
"http://192.168.8.1:8787",
"http://192.168.1.1:8787",
"http://10.0.0.1:8787",
"http://127.0.0.1:8787"
],
"healthPath": "/health",
"localMatchesPath": "/matches",
"cloudMatchesPath": "/api/serentech/matches",
"localStatePathTemplate": "/matches/:matchId/state",
"localStreamPathTemplate": "/stream/matches/:matchId/state",
"cloudStatePathTemplate": "/api/serentech/matches/:matchId/state",
"cloudStreamPathTemplate": "/api/serentech/matches/:matchId/stream",
"probeTimeoutMs": 700,
"localProbeBudgetMs": 2000,
"pollIntervalMs": 1000,
"staleAfterMs": 3000,
"subnetSweepHints": {
"enabled": true,
"scope": "realNetmask",
"port": 8787,
"healthPath": "/health",
"probeTimeoutMs": 2000,
"concurrency": 16,
"sweepBudgetMs": 20000,
"rfc1918HeuristicPrefix": {
"192.168.0.0/16": 24,
"10.0.0.0/8": 20,
"172.16.0.0/12": 20
}
},
"cloudBaseUrl": "https://api.mpfl.my",
"docsUrl": "https://api.mpfl.my/docs/serentech",
"overlayGuideUrl": "https://api.mpfl.my/docs/serentech/overlay.md"
},
"data": [
{
"matchId": "mpfl-2026-r01-jdt-sel-001",
"fixtureId": 101,
"roundId": 1,
"season": "2026",
"day": "Saturday, 16 May 2026",
"time": "14:00",
"home": "JDT",
"away": "SEL",
"homeCode": "JDT",
"awayCode": "SEL",
"homeTeam": {
"code": "JDT",
"name": "Johor Darul Ta'zim",
"crestUrl": "https://mpfl.my/assets/fam-mpfl-logo/mpfl26-team-logo/jdt-02.png?v=20260510-serentech",
"primaryColour": "#E2D505",
"color": "#E2D505",
"colorDark": "#1A1464",
"score": null,
"fouls": 0,
"coach": {
"name": "Coaching Staff TBC",
"photoUrl": ""
},
"officials": [
{
"id": "jdt-assistant-coach",
"no": 2,
"name": "Assistant Coach",
"role": "Assistant Coach"
}
],
"players": [
{
"id": "jdt-mohamad-awalluddin-mat-nawi",
"slug": "mohamad-awalluddin-mat-nawi",
"name": "Mohamad Awalluddin Mat Nawi",
"number": 5,
"position": "PV",
"starter": true,
"photoUrl": ""
}
]
},
"awayTeam": {
"code": "SEL",
"name": "Selangor FC",
"crestUrl": "https://mpfl.my/assets/fam-mpfl-logo/mpfl26-team-logo/sel-06.png?v=20260510-serentech",
"primaryColour": "#DD1E35",
"color": "#DD1E35",
"colorDark": "#8A0E1E",
"score": null,
"fouls": 0,
"coach": {
"name": "Coaching Staff TBC",
"photoUrl": ""
},
"officials": [],
"players": []
},
"graphics": {
"home": {
"code": "JDT",
"players": [
{
"id": "jdt-mohamad-awalluddin-mat-nawi",
"starter": true
}
]
},
"away": {
"code": "SEL",
"players": []
}
},
"matchup": {
"homeCode": "JDT",
"awayCode": "SEL",
"homeName": "Johor Darul Ta'zim",
"awayName": "Selangor FC",
"label": "JDT vs SEL",
"displayLabel": "Johor Darul Ta'zim vs Selangor FC"
},
"venue": "Pasir Gudang Indoor Stadium",
"status": "Preview",
"clockRemainingMs": null,
"score": {
"home": null,
"away": null,
"hasScore": false
},
"scorers": [],
"events": [
{
"type": "yellow_card",
"team": "JDT",
"playerId": "jdt-mohamad-awalluddin-mat-nawi",
"minute": 12
}
],
"standings": [
{
"pos": 1,
"club": "Johor Darul Ta'zim",
"code": "JDT",
"p": 0,
"w": 0,
"d": 0,
"l": 0,
"f": 0,
"a": 0,
"gd": "0",
"pts": 0,
"form": []
}
],
"stateUrl": "/api/serentech/matches/mpfl-2026-r01-jdt-sel-001/state",
"streamUrl": "/api/serentech/matches/mpfl-2026-r01-jdt-sel-001/stream"
}
]
}
Cloud state snapshot
{
"source": "edge",
"data": {
"matchId": "mpfl-2026-r01-jdt-sel-001",
"competition": "MPFL 2026",
"venue": "Pasir Gudang Indoor Stadium",
"period": "1st Half",
"clockRemainingMs": 719000,
"clockRunning": true,
"home": {
"code": "JDT",
"name": "Johor Darul Ta'zim",
"score": 2,
"fouls": 3,
"cards": "1Y"
},
"away": {
"code": "SEL",
"name": "Selangor FC",
"score": 1,
"fouls": 4,
"cards": ""
},
"timeoutTeamCode": null,
"lastSignal": null,
"eventCount": 42,
"updatedAt": "2026-05-10T13:00:00.000Z",
"edge": {
"addresses": [
"192.168.8.1",
"10.1.100.1"
],
"port": 8787
}
}
}
SSE state event
event: state
data: {"matchId":"mpfl-2026-r01-jdt-sel-001","competition":"MPFL 2026","venue":"Pasir Gudang Indoor Stadium","period":"1st Half","clockRemainingMs":719000,"clockRunning":true,"home":{"code":"JDT","name":"Johor Darul Ta'zim","score":2,"fouls":3,"cards":"1Y"},"away":{"code":"SEL","name":"Selangor FC","score":1,"fouls":4,"cards":""},"timeoutTeamCode":null,"lastSignal":null,"eventCount":42,"updatedAt":"2026-05-10T13:00:00.000Z","edge":{"addresses":["192.168.8.1","10.1.100.1"],"port":8787}}
Local Edge match discovery
{
"source": "edge",
"meta": {
"healthPath": "/health",
"localMatchesPath": "/matches",
"localStatePathTemplate": "/matches/:matchId/state",
"localStreamPathTemplate": "/stream/matches/:matchId/state",
"pollIntervalMs": 1000,
"staleAfterMs": 3000
},
"data": [
{
"matchId": "mpfl-2026-r01-jdt-sel-001",
"venue": "Pasir Gudang Indoor Stadium",
"home": "JDT",
"homeName": "Johor Darul Ta'zim",
"away": "SEL",
"awayName": "Selangor FC",
"status": "Live",
"stateUrl": "/matches/mpfl-2026-r01-jdt-sel-001/state",
"streamUrl": "/stream/matches/mpfl-2026-r01-jdt-sel-001/state"
}
]
}