Push Feeds
The feeds elsewhere in this guide are RESTful pull feeds: you request them on a schedule you control. The UFL API also offers two Push feeds, Push Events and Push Statistics, which hold a connection open and stream game updates to you as they happen. This page covers opening and filtering a connection, reading the stream, and recovering when a connection drops.
The Push Feeds
Each Push message is a JSON payload that mirrors the structure of a RESTful counterpart, so the parsing you build for the pull feeds carries over:
| Push feed | RESTful counterpart | What streams |
|---|---|---|
| Push Events | Game Play-by-Play | Every play and event as it is entered, with situations, per-play statistics, and details |
| Push Statistics | Game Statistics | Team and player box statistics, updated as plays resolve |
Push complements the pull feeds rather than replacing them. A Push session is not stateful: there is no way to request data the stream already sent, so pair Push with the RESTful game feeds for preloads, catch-up, and the validated final described in Game Status Workflow.
Push is a Realtime add-onThe Push Events and Push Statistics feeds are part of Sportradar's Realtime offering and are not included in every package. Contact your Sportradar representative to add Push to your package.
Opening a Connection
A Push connection starts as an ordinary GET with your API key. The response is an HTTP redirect to the stream, which then delivers chunked JSON indefinitely:
GET https://api.sportradar.com/ufl/{access_level}/stream/{language_code}/events/subscribe
x-api-key: YOUR_API_KEYGET https://api.sportradar.com/ufl/{access_level}/stream/{language_code}/statistics/subscribe
x-api-key: YOUR_API_KEYFor your application to accept the stream, ensure that it can:
- Follow an HTTP redirect (or use the
Locationheader from the initial response) within one minute of the request - Accept HTTP data transfer encoded as chunked
Filtering the Stream
An unfiltered connection streams every game in progress. Query string parameters narrow the stream, and each parameter is added with a preceding ampersand:
| Parameter | Values | Use |
|---|---|---|
match | match=sd:match:{game_guid} | One game |
team | team=sd:team:{team_guid} | One team's games |
status | inprogress, created | Games in a given status |
event_type | setup, timeout, tv_timeout, two_minute_warning, comment, period_end, game_over | Specific event types |
event_category | redzone, two_minute, scoring_play, big_play, turnover | High-interest situations |
GET https://api.sportradar.com/ufl/trial/stream/en/events/subscribe?&status=inprogress&match=sd:match:c5cce400-a716-49d0-bbbe-1a84fb200fa3
x-api-key: YOUR_API_KEYThe sd: prefix wraps the same GUID the RESTful paths use; ID Handling covers the ID families.
Reading the Stream
Messages arrive one JSON document at a time. When no game data is flowing, a heartbeat arrives every 5 seconds to keep the connection alive; anything else is a payload message:
{
"heartbeat": {
"interval": 5000
}
}A Push Events payload pairs a game envelope (score, clock, quarter) with the event that just changed. The opening kickoff of a week 1 game shows the shape; the full message carries the same period, drive, situation, statistics, and details nodes as the RESTful Play-by-Play, trimmed here to the envelope:
{
"payload": {
"game": {
"id": "c5cce400-a716-49d0-bbbe-1a84fb200fa3",
"status": "inprogress",
"coverage": "full",
"game_type": "regular",
"scheduled": "2026-03-28T16:00:00+00:00",
"quarter": 1,
"clock": "14:55",
"summary": {
"home": {
"name": "Battlehawks",
"market": "St. Louis",
"alias": "STL",
"points": 0
},
"away": {
"name": "Defenders",
"market": "DC",
"alias": "DC",
"points": 0
}
},
"sr_id": "sr:match:68165532"
},
"event": {
"type": "play",
"id": "c8992780-2aba-11f1-bb44-7d8e81a24ff9",
"sequence": 1774713956580,
"clock": "15:00",
"home_points": 0,
"away_points": 0,
"created_at": "2026-03-28T16:05:56+00:00",
"updated_at": "2026-03-28T16:05:57+00:00",
"play_type": "kickoff",
"description": "M.McCrane kicks 65 yards from DC 30 to the STL 5. G.Jennings returns the kickoff. Tackled by DC at STL 31."
}
},
"locale": "en",
"metadata": {
"match": "sd:match:c5cce400-a716-49d0-bbbe-1a84fb200fa3,sr:match:68165532",
"team": "sd:team:a3407150-c045-11ee-b66f-5d382d09dbaf,sr:competitor:661851",
"league": "ufl",
"locale": "en",
"status": "inprogress",
"event_type": "kickoff",
"event_category": "",
"operation": "update",
"version": "v7"
}
}{
"payload": {
"event": {
"type": "play",
"id": "c8992780-2aba-11f1-bb44-7d8e81a24ff9",
"clock": "15:00",
"updated_at": "2026-03-28T16:06:08+00:00",
"play_type": "kickoff",
"description": "M.McCrane kicks 65 yards from DC 30 to the STL 5. G.Jennings returns the kickoff. Tackled by D.Parish at STL 31."
}
},
"locale": "en",
"metadata": {
"match": "sd:match:c5cce400-a716-49d0-bbbe-1a84fb200fa3,sr:match:68165532",
"team": "sd:team:a3407150-c045-11ee-b66f-5d382d09dbaf,sr:competitor:661851",
"league": "ufl",
"locale": "en",
"status": "inprogress",
"event_type": "kickoff",
"event_category": "",
"operation": "update",
"version": "v7"
}
}The second tab is the same kickoff moments later: the stream re-sent event c8992780-... once the tackler was attributed (the description now names D.Parish, and updated_at moved). When a message carries an event id you have already consumed, replace the stored event with the new message. The metadata.operation value distinguishes an update (create or revise) from a delete, which tells you to remove the stored event instead.
Statistics Messages
Push Statistics streams two message shapes. Player messages carry the player and the statistics category a play just changed, and are small enough to apply directly. Team messages carry the full category set for one team; at kickoff they arrive as the game's opening baseline (trimmed here to the rushing category):
{
"payload": {
"player": {
"full_name": "Gary Jennings Jr.",
"first_name": "Gary",
"last_name": "Jennings",
"name_suffix": "Jr.",
"id": "ab7edd60-cc67-11ee-a60a-8d7f07d3367e",
"position": "WR",
"name": "Gary Jennings Jr.",
"jersey": "05"
},
"kick_returns": {
"yards": 26,
"avg_yards": 26,
"touchdowns": 0,
"longest": 26,
"faircatches": 0,
"number": 1
}
},
"locale": "en",
"metadata": {
"match": "sd:match:c5cce400-a716-49d0-bbbe-1a84fb200fa3,sr:match:68165532",
"league": "ufl",
"locale": "en",
"status": "inprogress",
"participant": "sd:player:ab7edd60-cc67-11ee-a60a-8d7f07d3367e",
"operation": "update",
"version": "v7"
}
}{
"payload": {
"name": "Defenders",
"market": "DC",
"alias": "DC",
"id": "c5cce400-a716-49d0-bbbe-1a84fb200fa3",
"status": "inprogress",
"quarter": 1,
"clock": "15:00",
"rushing": {
"avg_yards": 0,
"attempts": 0,
"touchdowns": 0,
"tlost": 0,
"tlost_yards": 0,
"yards": 0,
"redzone_attempts": 0,
"broken_tackles": 0,
"kneel_downs": 0,
"scrambles": 0,
"yards_after_contact": 0,
"firstdowns": 0
},
"sr_id": "sr:match:68165532"
},
"locale": "en",
"metadata": {
"match": "sd:match:c5cce400-a716-49d0-bbbe-1a84fb200fa3,sr:match:68165532",
"league": "ufl",
"locale": "en",
"status": "inprogress",
"participant": "sd:team:a3407150-c045-11ee-b66f-5d382d09dbaf",
"operation": "update",
"version": "v7"
}
}Two details of the team shape are worth noting: the top-level id and sr_id are the game's GUID and URN (the team is identified by name, market, and alias), and the message subject rides metadata.participant on both shapes (sd:team: or sd:player: wrapping the entity GUID).
Metadata and Routing
Every payload closes with a metadata block: the league, the game, the operation, and the event type or participant the message concerns. Comma-delimited values pair the sd:-prefixed GUID with the sr: URN for the same entity, so read them as an array:
"match": "sd:match:c5cce400-...,sr:match:68165532"is one game under both ID families- Route messages by
metadata.matchbefore parsing the payload when you track several games on one connection - Filter high-interest moments client-side with
event_typeandevent_category, which carry the same values the query string parameters accept
Recovering from Disconnections
If heartbeats stop arriving, or the connection drops for any reason, reconnect with the same subscribe request. The stream resumes with current data only, so close the gap from the RESTful side:
- Pull Game Play-by-Play to recover plays missed on Push Events
- Pull Game Statistics to re-baseline after a gap on Push Statistics
- Keep the Tracking Live Games polling loop as the fallback path; it delivers the same game detail at pull cadence
Testing Push
Simulations replay UFL games on demand, including both Push feeds, so you can exercise connection handling, message parsing, and recovery at any time of year without waiting for the spring schedule.
Updated 11 days ago
