Oddin.gg Bragi - Data Feed documentation
Introduction
At Oddin.gg we believe the future belongs to Esports, and we want to help drive that future. Bragi is our data feed: it streams real-time information for every match we distribute. For each match we provide the current live state, score, team and player statistics, and a great deal of supporting information that you can use to power your own products.
1. General information
This chapter explains the core concepts behind the Bragi data feed. If anything below is unclear, please get in touch through your usual Oddin.gg communication channel.
1.1. Concept
The API is built on gRPC and protocol buffers. gRPC is a proven, high-performance framework used at scale by Google, Microsoft and many others. It generates client and server bindings for most popular languages, including Java, C#, Python, Node.js, Go and others. Protocol buffers are used as the message format: they are compact, fast to encode and decode, and well supported across the industry.
- More about gRPC: https://grpc.io
- More about protocol buffers: https://developers.google.com/protocol-buffers
1.2. Environments
Two environments are available. Use the integration environment for all development and testing, and only move to the production environment once your integration is complete and ready for live traffic.
| Environment | API URL |
| Production | api-bragi.oddin.gg:443 |
| Integration | api-bragi.integration.oddin.gg:443 |
1.3. gRPC schema definition
The full schema is open and published on GitHub:
- Service description: https://github.com/oddin-gg/bragischema
- Protocol buffers schema: https://github.com/oddin-gg/bragischema/tree/main/proto/bragi
You can generate client bindings for your language of choice directly from the .proto files or use one of our pre-generated bindings for Java, Go, or Node.js.
1.4. Authentication
- IP whitelisting: access to the API is allowed only from known IP addresses. Each environment has its own whitelist. Send the IPs (or IP ranges) you want to register, together with the environment name, to your technical integration contact.
- Access token: every call to the data feed must carry a valid access token. You receive a separate token for each environment from our back-office. Tokens are normally valid for one year and are auto-extended.
ℹ️ A maximum of fifty (50) concurrent connections can be made to any of the streams, applied separately to each environment.
1.5. Service definition
The Bragi service exposes both unary calls (single request, single response) and server streams (single request, ongoing stream of responses). The table below summarises every remote procedure call (RPC) defined in the schema.
| RPC | Type | Description |
MatchTimelineSports |
Unary | Returns every sport together with the count of planned or currently played matches. |
MatchTimelineTournaments |
Unary | Returns every tournament together with the count of planned or currently played matches. |
MatchTimeline |
Unary | Returns every planned or currently played match. |
TeamProfile |
Unary | Returns comprehensive information for a single team, including external identities and the current map roster. |
MatchTimelineSportsFeed |
Stream | Streams real-time updates of the per-sport match counts. |
MatchTimelineTournamentsFeed |
Stream | Streams real-time updates of the per-tournament match counts. |
MatchTimelineFeed |
Stream | Streams every upcoming and live match, plus real-time updates whenever a match status changes. Stay connected to keep your timeline current. |
LiveDataFeed |
Stream | Streams LiveDataFeedMessage frames in a single direction (server to client). This is the main live match-state stream. |
MatchEventsFeed |
Stream | Streams historical events for currently played matches, followed by real-time event updates. |
2. Match timeline
MatchTimeline provides three categories of gRPC endpoints for retrieving real-time and planned match data: Sports, Tournaments and Matches. Each category exposes a unary endpoint for one-off requests and a server-streamed endpoint for continuous updates.
| Endpoint category | Purpose | Unary endpoint | Stream endpoint | Key filtering parameters |
| Sports | List sports and per-sport match counts. | MatchTimelineSports |
MatchTimelineSportsFeed |
live_only (boolean) |
| Tournaments | List tournaments and per-tournament match counts. | MatchTimelineTournaments |
MatchTimelineTournamentsFeed |
live_only (boolean), sport (enum) |
| Matches | List every current and planned match. | MatchTimeline |
MatchTimelineFeed |
live_only, sport, tournament_urn, match_urn, team_name |
Both count-based endpoints (Sports and Tournaments) use the live_only parameter to choose whether the count should include all matches or only live matches. The Matches endpoints offer the richest filtering, including by URN, sport and team name.
2.1. MatchTimeline sports
Two gRPC endpoints return the list of sports together with a count of current and upcoming matches per sport. The count returned depends on the live_only flag in the request:
- MatchTimelineSports — a one-off (unary) call.
- MatchTimelineSportsFeed — a stream that pushes updates as match counts change.
MatchTimelineSportsRequest / MatchTimelineSportsFeedRequest
| Parameter | Type | Description |
live_only |
bool |
If false, the endpoint returns the total count of all matches for a given sport. If true, only live matches are counted. |
MatchTimelineSportsResponse / MatchTimelineSportsFeedResponse
| Field | Type | Description |
sports |
repeated SportMatchCount |
List of sports with their corresponding match counts. |
SportMatchCount (nested message)
| Field | Type | Description |
sport |
Sport |
The sport this row refers to. See the Sport enum below. |
match_count |
uint32 |
Number of matches for that sport, filtered by live_only. |
Example JSON — request and response
// MatchTimelineTournamentsRequest
{
"live_only": false,
"sport": "SPORT_CS2"
}
// MatchTimelineTournamentsResponse
{
"tournaments": [
{
"tournament": {
"urn": "od:tournament:1001",
"name": "ESL Pro League",
"is_offline": false,
"start_date": "2026-03-15T14:00:00Z",
"organizer_id": 42,
"organizer_name": "ESL"
},
"match_count": 24
}
]
}
2.2. MatchTimeline tournaments
Two gRPC endpoints return the list of tournaments together with a count of current and upcoming matches per tournament. The count returned depends on the live_only flag in the request:
- MatchTimelineTournaments — a one-off (unary) call.
- MatchTimelineTournamentsFeed — a stream that pushes updates as match counts change.
MatchTimelineTournamentsRequest / MatchTimelineTournamentsFeedRequest
| Parameter | Type | Description |
live_only |
bool |
If false, returns the total count for each tournament. If true, only live matches are counted. |
sport |
Sport (enum) |
Restricts the response to tournaments belonging to the given sport. |
MatchTimelineTournamentsResponse / MatchTimelineTournamentsFeedResponse
| Field | Type | Description |
tournaments |
repeated TournamentMatchCount |
List of tournaments with their match counts. |
TournamentMatchCount (nested message)
| Field | Type | Description |
tournament |
Tournament |
The tournament this row refers to. |
match_count |
uint32 |
Number of matches for that tournament, filtered by live_only. |
Example JSON — request and response
// MatchTimelineTournamentsRequest
{
"live_only": false,
"sport": "SPORT_CS2"
}
// MatchTimelineTournamentsResponse
{
"tournaments": [
{
"tournament": {
"urn": "od:tournament:1001",
"name": "ESL Pro League",
"is_offline": false,
"start_date": "2026-03-15T14:00:00Z",
"organizer_id": 42,
"organizer_name": "ESL"
},
"match_count": 24
}
]
}
2.3. MatchTimeline matches
Two gRPC endpoints return every currently played and planned match:
- MatchTimeline — a oneof (unary) call.
- MatchTimelineFeed — a stream that automatically pushes updates whenever a match status changes.
ℹ️ The MatchTimelineFeed endpoint may include matches that have not yet started. For those matches, you will not have received any update message through the LiveDataFeed yet.
MatchTimelineRequest / MatchTimelineFeedRequest
| Parameter | Required | Type | Description |
live_only |
No | bool |
When true, only live matches are returned. |
sport |
No | Sport (enum) |
Restricts the response to a single sport. |
tournament_urn |
No | string |
Unique identifier of a tournament, for example od:tournament:12345. |
match_urn |
No | string |
Unique identifier of a match. A partial URN such as od:match:23 returns every match whose URN starts with that string. |
team_name |
No | string |
A substring; matches whose team names contain it are returned. |
MatchTimelineResponse
| Field | Type | Description |
matches |
repeated Match |
List of matches that satisfy the supplied filters. |
MatchTimelineFeedMessage
Wraps a single frame on the MatchTimelineFeed stream. Exactly one of the oneof fields below is set per message.
| Field | Type | Description |
keepalive |
KeepAlive |
Deprecated. Replaced by the built-in gRPC keep-alive (see section 3.4). |
timeline |
MatchTimeline |
A full timeline snapshot containing the current set of matches. |
match_update |
Match |
An incremental update for a single match. |
Example JSON — MatchTimelineFeedMessage
// Initial timeline frame
{
"timeline": {
"matches": [
{
"match_urn": "od:match:55501",
"sport": "SPORT_CS2",
"home_score": 0,
"away_score": 0,
"match_status": "MATCH_STATUS_TYPE_NOT_STARTED",
"planned_start": "2026-05-27T18:00:00Z",
"tournament": { "urn": "od:tournament:1001", "name": "ESL Pro League" },
"teams": [
{ "urn": "od:team:101", "name": "Team Alpha", "is_home": true },
{ "urn": "od:team:202", "name": "Team Bravo", "is_home": false }
]
}
]
}
}
// Subsequent update frame
{
"match_update": {
"match_urn": "od:match:55501",
"sport": "SPORT_CS2",
"home_score": 1,
"away_score": 0,
"match_status": "MATCH_STATUS_TYPE_LIVE"
}
}
2.4. Common match data structures
The messages below are returned across the MatchTimeline endpoints and are reused throughout the schema. They describe the basic shape of a match, the tournament it belongs to, the teams involved and the players on those teams.
MatchTimeline
| Field | Type | Description |
matches |
repeated Match |
The list of matches in this timeline snapshot. |
Match
| Field | Type | Description |
match_urn |
string |
Unique match identifier, e.g., od:match:55501. |
tournament_urn |
string |
Deprecated. Use tournament instead. |
sport |
Sport |
Sport this match belongs to. |
home_team_urn |
string |
Deprecated. Use teams instead. |
away_team_urn |
string |
Deprecated. Use teams instead. |
home_score |
uint32 |
Current score of the home team. |
away_score |
uint32 |
Current score of the away team. |
match_status |
MatchStatusType |
Current status (not started, live, finished, cancelled or placeholder). |
plannet_start |
google.protobuf.Timestamp |
Planned start time of the match (UTC). |
tournament |
Tournament |
Tournament the match belongs to. |
teams |
repeated Team |
Teams taking part in the match. |
players |
repeated Player |
Players taking part in the match. |
Tournament
| Field | Type | Description |
urn |
string |
Unique tournament identifier, e.g., od:tournament:1001. |
name |
string |
Display name. |
is_offline |
bool |
True if the tournament is held offline (LAN), false otherwise. |
start_date |
google.protobuf.Timestamp |
Tournament start date and time (UTC). |
organizer_id |
uint32 |
Internal identifier of the tournament organizer. |
organizer_name |
string |
Display name of the organizer. |
Team
| Field | Type | Description |
urn |
string |
Unique team identifier, e.g., od:team:123. |
name |
string |
Team display name. |
is_home |
bool |
True if the team is the home team. |
Player
| Field | Type | Description |
urn |
string |
Unique player identifier, e.g., od:player:123. |
nickname |
string |
Player's in-game nickname. |
team_urn |
string |
URN of the team the player belongs to. |
3. Processing the data feed
3.1. Connection to the data feed
To open the live data stream, the client calls the LiveDataFeed RPC and sends a LiveDataFeedRequest. LiveDataFeedRequest has a single optional timestamp field named after.
When connecting for the first time, leave the after field unset (null). The server then sends the data for every currently played match. If you want to receive matches older than those currently in play, set after to the timestamp of the last update you processed (UNIX milliseconds). The server will then send data for the matches whose latest update was processed at or after that timestamp.
LiveDataFeedRequest
| Field | Type | Description |
after |
google.protobuf.Timestamp (optional) |
Optional. The timestamp from which match states should be received. If unset, only currently played matches are returned. If set, the server returns matches whose latest update was processed at or after this value. |
Example JSON — LiveDataFeedRequest
// First-time connection (no "after")
{}
// Reconnect, resuming from the last processed update
{
"after": "2026-05-27T17:32:11.250Z"
}
3.2. Supported Esports and eSims
Throughout this documentation, the placeholder (Game) is used to refer to any one of the supported Esports or eSims. To use the schema for a specific Esport or eSim, replace (Game) with the relevant prefix (for example, CS2MatchMessage).
| Esport | Description | Active |
| CS2 | Counter-Strike 2 | ✅ Yes |
| DOTA2 | Dota 2 | ✅ Yes |
| LOL | League of Legends | ❌ No |
| VALORANT | Valorant | ❌ No |
| RUSH_BASKETBALL | eBasketball | ✅ Yes |
| RUSH_SOCCER | eFootball | ✅ Yes |
| RUSH_CRICKET | eCricket | ✅ Yes |
3.3. Match state caching
The stream carries information for many matches and several different Esports. Each match is identified by a unique match URN. Your client is expected to process the incoming messages and keep the current state of every active match in its cache.
A match snapshot contains everything the server knows about a match. The client receives one snapshot per match at the start of the stream and keeps it up to date by applying the updates that follow. The details of this update process are described in (Game)MatchSnapshot message below.
3.4. Keepalive
To keep the live feed connection healthy, configure the keepalive settings in your gRPC client. The recommended values are listed below.
| Parameter | Value | Description | Behaviour |
Time |
10 seconds |
How often the client sends keepalive pings to the server. | A ping is sent every 10 seconds. |
Timeout |
5 seconds |
How long the client waits for a pong before considering the connection broken. | If no response arrives within 5 seconds, the connection is treated as dead. |
PermitWithoutStream |
true |
Whether keepalive pings may be sent when there is no active RPC. | Keepalive pings are still sent during idle periods, which keeps the connection open. |
ℹ️ Make sure these parameters are set in your gRPC client to keep the live data feed connection stable and responsive.
Sample code (Go)
grpc.WithKeepaliveParams(
keepalive.ClientParameters{
Time: 10 * time.Second,
Timeout: 5 * time.Second,
PermitWithoutStream: true,
},
)
3.5. Supported messages
This section describes the protocol buffer messages that flow on the live data feed and how to process them.
3.5.1. LiveDataFeedMessage
LiveDataFeedMessage is the wrapping envelope for every message you receive from the server.
| Field | Type | Description |
keepalive |
KeepAlive |
Deprecated. Replaced by the built-in gRPC keep-alive. |
match |
MatchMessage |
Carries the information for a particular match (either an initial snapshot, an update, or an announcement). |
3.5.2. MatchMessage
MatchMessage uses a protocol buffer oneof to carry either an AnnouncementUpdate or a game-specific (Game)MatchMessage. The fields below show the available payload types. Each frame from the server sets exactly one of these fields.
| Field | Type | Description |
announcement |
AnnouncementUpdate |
Append the announcement payload to the announcements array of the cached (Game)MatchSnapshot. |
cs2 |
CS2MatchMessage |
Wraps a CS2MatchSnapshot or CS2MatchUpdate for a CS2 match. |
dota2 |
Dota2MatchMessage |
Wraps a Dota2MatchSnapshot or Dota2MatchUpdate for a Dota 2 match. |
lol |
LolMatchMessage |
Wraps a LolMatchSnapshot or LolMatchUpdate for a League of Legends match. |
valorant |
ValorantMatchMessage |
Wraps a ValorantMatchSnapshot or ValorantMatchUpdate for a Valorant match. |
rush_soccer |
RushSoccerMatchMessage |
Wraps a RushSoccerMatchSnapshot or RushSoccerMatchUpdate for an eFootball match. |
rush_basketball |
RushBasketballMatchMessage |
Wraps a RushBasketballMatchSnapshot or RushBasketballMatchUpdate for an eBasketball match. |
rush_cricket |
RushCricketMatchMessage |
Wraps a RushCricketMatchSnapshot or RushCricketMatchUpdate for an eCricket match. |
active_data_source_type* |
uint32 |
Identifier of the active data source that produced this message. *Not yet supported. |
3.5.2.1. AnnouncementUpdate message
An AnnouncementUpdate informs your client about operational events during the life of a match — for example, the match has been postponed, or there will be a server outage. The AnnouncementUpdate wraps an Announcement and adds sequence and timestamp fields. When you receive one, append the Announcement payload to the announcements array on the cached (Game)MatchSnapshot.
AnnouncementUpdate
| Field | Type | Description |
match_urn |
string |
Match this announcement applies to. |
sequence |
uint64 |
Sequence number; used to detect missing updates (see section 3.5.2.2.1). |
timestamp |
google.protobuf.Timestamp |
Time the update was produced (UTC). |
payload |
Announcement |
The announcement itself. |
Announcement
| Field | Type | Description |
created_at |
google.protobuf.Timestamp |
Time the announcement was created. |
payload |
Announcement.Payload |
Oneof: either a ControlAnnouncement or a ControlErrorAnnouncement. |
ControlAnnouncement
| Field | Type | Description |
type |
ControlAnnouncementType |
The kind of control announcement. |
message |
string (optional) |
Free-form human-readable description. |
ControlErrorAnnouncement
| Field | Type | Description |
type |
ControlErrorAnnouncementType |
The kind of error announcement. |
message |
string (optional) |
Free-form human-readable description. |
Example JSON — AnnouncementUpdate
{
"match_urn": "od:match:55501",
"sequence": 432,
"timestamp": "2026-05-27T17:34:02.110Z",
"payload": {
"created_at": "2026-05-27T17:34:02.000Z",
"payload": {
"control": {
"type": "CONTROL_ANNOUNCEMENT_TYPE_UNSPECIFIED",
"message": "Match postponed by 15 minutes."
}
}
}
}
3.5.2.2. (Game)MatchMessage message
Each sport has its own MatchMessage type — for example, CS2MatchMessage or Dota2MatchMessage. Protocol buffers do not support interfaces or traits, so there is no generic MatchMessage type at the schema level; we use the (Game)MatchMessage label only in this documentation.
Every (Game)MatchMessage has exactly four properties. Three are common to all sports (match_urn, sequence, timestamp); the fourth, payload, is sport-specific (for example, CS2MatchState).
| Property | Description |
match_urn |
Identifies the match this message applies to. |
sequence |
A positive integer used to confirm that updates arrive in order. See (Game)MatchSnapshot message below for the full rules. |
timestamp |
Timestamp of the latest update for this match. See (Game)MatchSnapshot message below for how to use it. |
payload |
Oneof. Carries either: • (Game)MatchSnapshot — sent only as the first message for a match. Cache it in memory, then apply later updates to it. • (Game)MatchUpdate — sent after the initial snapshot. Updates are applied to the cached snapshot. |
The processing rules are the same across every sport. For brevity, the explanation below uses CS2; the same approach applies to Dota 2, eBasketball and the other games. See Match state processing in section 3.5.2.2.1.1 for more detail.
CS2MatchMessage (representative example of a (Game)MatchMessage)
| Field | Type | Description |
match_urn |
string |
Match the message refers to. |
sequence |
uint64 |
Sequence number for ordering and gap detection. |
timestamp |
google.protobuf.Timestamp |
Time the message was produced. |
data_status |
DataStatus |
Quality flag for the underlying data (valid, disabled or corrupted). |
payload |
CS2MatchMessage.Payload |
Oneof: either snapshot (CS2MatchSnapshot) or update (CS2MatchUpdate). |
3.5.2.2.1. (Game)MatchSnapshot message
Each sport has its own snapshot type — for example, CS2MatchSnapshot. Because protocol buffers cannot express a common interface, the schema does not define a generic MatchSnapshot; we use that label only here.
A (Game)MatchSnapshot has exactly five properties. Four are common across all sports (match_urn, sequence, timestamp, announcements); the fifth, match_state, is sport-specific (for example, CS2MatchState).
The snapshot represents the full current state of a match. You receive it exactly once per match, and it is always the first (Game)MatchMessage you see for that match after connecting. Cache the snapshot in memory and apply later update messages (for example, CS2MatchMessage or AnnouncementUpdate) on top of it.
| Property | Description |
match_urn |
Match identification. |
sequence |
A positive integer used to confirm updates arrive in order. Every later update message carries a sequence value that must equal the cached sequence + 1. If it does not, something went wrong and the client should restart the stream. After applying an update, replace the cached sequence with the value from the update. |
timestamp |
Tied to sequence: the timestamp of the most recent applied update. After applying an update, replace the cached timestamp with the value from the update. If you need to reconnect, send a new LiveDataFeedRequest with after set to the cached timestamp. |
announcements |
Array of Announcement messages that inform the consumer about operational events during the match life — for example, a postponement or a server outage. |
match_state |
Sport-specific current state of the match and its statistics. All future updates are applied to this field. |
CS2MatchSnapshot (representative example of a (Game)MatchSnapshot)
| Field | Type | Description |
match_urn |
string |
Match identifier. |
sequence |
uint64 |
Initial sequence number for this match. |
timestamp |
google.protobuf.Timestamp |
Time of this snapshot. |
data_status |
DataStatus |
Quality flag for the underlying data. |
announcements |
repeated Announcement |
Operational announcements that apply to the match. |
match_state |
CS2MatchState |
Full current state of the CS2 match. |
tournament |
Tournament |
Tournament the match belongs to. |
3.5.2.2.1.1. Match state processing
Match state represents the current state of a match and is cached in the match_state property of the (Game)MatchSnapshot. For simplicity, the rules below are described for CS2 (CS2MatchState), but they apply to every other sport in the same way.
| Property type | Description |
sub-states |
States defined by a protobuf message whose name ends with the suffix State. We refer to these as sub-states (for example, CS2MatchScoreState). A sub-state may contain nested sub-states or protobuf maps where the key is a URN of the sub-state and the value is the sub-state itself (for example, CS2MapState has a map of CS2TeamMapState). These properties can be deeply nested. Sub-states represent groups of fields that change often during a match (for example, player position). To make partial updates simple, the server sends an idempotent update for each sub-state; you replace the cached sub-state with the version you receive. |
scalar protobuf type |
Fields that change less often, or that already live inside another sub-state, are exposed directly as scalar protobuf types (see the protobuf scalar types documentation). The value is updated only when the enclosing state or sub-state is replaced. For example: map_order with type uint32. |
scalar |
Properties that are protobuf messages but whose names do not end with State. These are treated as scalar properties (described above). |
ℹ️ (Game)MatchState is effectively a sub-state of (Game)MatchSnapshot — clients should treat it the same way.
3.5.2.2.2. Updates
Updates are carried inside (Game)MatchMessage messages. The rules below are explained for CS2, but they apply to every other sport in the same way.
Bear in mind that the stream carries messages for many matches at once. We describe the update flow for a single match here, but in practice your client will maintain many match states and their cached snapshots (for example, CS2MatchSnapshot). The rules for applying updates are identical for each.
CS2MatchMessage is the parent of both the initial snapshot and every later update. It carries the sequence and timestamp fields (see (Game)MatchSnapshot for how to apply them to the cached state), a match_urn that identifies the match the update is for, and a oneof payload property with two variants:
| Type | Description |
(Game)MatchSnapshot |
Cache this in memory when you receive it. You will receive it exactly once per match. To recover it later, you must restart the stream. |
(Game)MatchUpdate |
Apply this to the match_state property of the cached snapshot. |
3.5.2.2.2.1. (Game)MatchUpdate message
A (Game)MatchUpdate carries content that should be applied to the match_state property of the cached (Game)MatchSnapshot. The update arrives as the payload of a (Game)MatchMessage, which provides the sequence and timestamp fields. Once you have applied the update, replace the cached sequence and timestamp values with the ones from the (Game)MatchMessage.
⚠️ If any update is missing (the sequence number is not exactly one greater than the cached value), the client must recover the whole snapshot by restarting the connection.
A (Game)MatchUpdate contains two fields: payload and events.
| Property | Description |
payload |
Oneof with two variants: • (Game)MatchState — an idempotent message containing the entire match state. Replace the match_state field of the cached (Game)MatchSnapshot with this value. • (Game)MatchStatePartialUpdates — a wrapper around an array of (Game)MatchStatePartialUpdate messages. See section 3.5.2.2.2.1.1 below. |
events (deprecated) |
Array of zero or more (Game)Event messages that describe the history of what happened during the match (for example, Player 1 was killed by Player 2). (Game)Event messages are non-idempotent and are NOT applied to the match state. You can either ignore them or process them as you wish, but be aware that there is no way to recover missed events. Note: events are deprecated; use the MatchEventsFeed described in section 4 instead. |
ℹ️ For example, when the position of Player 1 changes, the client receives a CS2MatchUpdate with an empty events array and a payload of type CS2MatchStatePartialUpdates containing a single partial update that wraps a CS2PlayerPositionState. When Player 1 kills Player 2, the client receives a CS2MatchUpdate with two CS2Events (a CS2Kill and a CS2Death) and two partial updates that wrap CS2PlayerCurrentRoundStatisticsState, one for each player.
As described above, the payload of a (Game)MatchUpdate is always either a (Game)MatchState or a non-empty array of partial updates. The payload always carries information about a change to the match state. The events field, by contrast, can be empty.
CS2MatchUpdate (representative example)
| Field | Type | Description |
payload |
CS2MatchUpdate.Payload |
Oneof: match_state (a full CS2MatchState) or partial_updates (CS2MatchStatePartialUpdates). |
events |
repeated CS2Event |
Deprecated. Use MatchEventsFeed. |
3.5.2.2.2.1.1. (Game)MatchStatePartialUpdates message
(Game)MatchStatePartialUpdates wraps a non-empty array of idempotent updates to be applied to the match_state property (of type (Game)MatchState) inside (Game)MatchSnapshot. We call each item a partial update (for example, CS2MatchStatePartialUpdate). A partial update is a message containing a sub-state update (which replaces a specific cached sub-state) plus the set of URN properties that locate that sub-state. Note that a partial update can itself wrap another partial update (for example, CS2MatchStatePartialUpdate can wrap CS2CurrentMapStatePartialUpdate).
CS2MatchStatePartialUpdates
| Field | Type | Description |
updates |
repeated CS2MatchStatePartialUpdate |
Non-empty list of partial updates to apply, in order. |
3.5.2.2.2.1.2. How to apply partial updates
A partial update is either a oneof protobuf message or a protobuf message with a payload field that is itself a oneof. Each partial update is a wrapper around some sub-state — which replaces a specific cached sub-state — and its URNs. A partial update can wrap another partial update, but the innermost level is always a sub-state.
Partial update message names start with the type they update and end with the suffix PartialUpdate (for example, CS2MatchStatePartialUpdate is applied to the CS2MatchState type). As noted above, a partial update is itself a oneof message, or it contains a nested Payload type that is a oneof. The name of the oneof field matches the name of the property being updated.
Here is a worked example. When the current round's score changes, the client receives a CS2MatchMessage carrying a CS2MatchUpdate with one CS2MatchStatePartialUpdate in the updates array. That partial update is of type CS2MatchScoreState. The fact that the type is CS2MatchScoreState tells you it can only replace CS2MatchScoreState properties, and its score field tells you exactly which property to overwrite.
Example JSON — partial update flow
// CS2MatchMessage carrying a CS2MatchUpdate with one partial update
{
"match_urn": "od:match:55501",
"sequence": 1024,
"timestamp": "2026-05-27T18:14:55.000Z",
"data_status": "DATA_STATUS_VALID_DATA",
"payload": {
"update": {
"payload": {
"partial_updates": {
"updates": [
{
"score": {
"home_score": 9,
"away_score": 6
}
}
]
}
}
}
}
}
3.5.3. MatchSnapshot and MatchState helper wrappers
The schema also defines two helper oneof messages that join all (Game)MatchSnapshot and (Game)MatchState types into a single union. These are convenient when you want a generic container; they are NOT required for maintaining or rebuilding match state, which always uses the game-specific types directly.
MatchSnapshot
| Field | Type | Description |
cs2 |
CS2MatchSnapshot |
Snapshot for a CS2 match. |
dota2 |
Dota2MatchSnapshot |
Snapshot for a Dota 2 match. |
lol |
LolMatchSnapshot |
Snapshot for a League of Legends match. |
valorant |
ValorantMatchSnapshot |
Snapshot for a Valorant match. |
rush_soccer |
RushSoccerMatchSnapshot |
Snapshot for an eFootball match. |
rush_basketball |
RushBasketballMatchSnapshot |
Snapshot for an eBasketball match. |
rush_cricket |
RushCricketMatchSnapshot |
Snapshot for an eCricket match. |
MatchState
| Field | Type | Description |
cs2 |
CS2MatchState |
Match state for a CS2 match. |
dota2 |
Dota2MatchState |
Match state for a Dota 2 match. |
lol |
LolMatchState |
Match state for a League of Legends match. |
valorant |
ValorantMatchState |
Match state for a Valorant match. |
rush_soccer |
RushSoccerMatchState |
Match state for an eFootball match. |
rush_basketball |
RushBasketballMatchState |
Match state for an eBasketball match. |
rush_cricket |
RushCricketMatchState |
Match state for an eCricket match. |
3.5.4. KeepAlive (deprecated)
KeepAlive was originally a heartbeat message sent periodically from the server. It has been replaced by the built-in gRPC keep-alive (see section 3.4.) and remains in the schema for backwards compatibility.
| Field | Type | Description |
timestamp |
google.protobuf.Timestamp |
Time at which the heartbeat was sent. |
3.5.5. Common enumerations
The enums below are shared across many of the messages described elsewhere in the schema.
Sport
| Value | Description |
SPORT_UNSPECIFIED |
Default value; never expected in practice. |
SPORT_CS2 |
Counter-Strike 2. |
SPORT_DOTA2 |
Dota 2. |
SPORT_LOL |
League of Legends. |
SPORT_VALORANT |
Valorant. |
SPORT_RUSH_SOCCER |
eFootball. |
SPORT_RUSH_BASKETBALL |
eBasketball. |
SPORT_RUSH_CRICKET |
eCricket. |
BestOfType
| Value | Description |
BEST_OF_TYPE_UNSPECIFIED |
Default value. |
BEST_OF_TYPE_BO1 |
Best of 1. |
BEST_OF_TYPE_BO2 |
Best of 2. |
BEST_OF_TYPE_BO3 |
Best of 3. |
BEST_OF_TYPE_BO4 |
Best of 4. |
BEST_OF_TYPE_BO5 |
Best of 5. |
BEST_OF_TYPE_BO6 |
Best of 6. |
BEST_OF_TYPE_BO7 |
Best of 7. |
MatchStatusType
| Value | Description |
MATCH_STATUS_TYPE_UNSPECIFIED |
Default value. |
MATCH_STATUS_TYPE_NOT_STARTED |
Match has not yet started. |
MATCH_STATUS_TYPE_LIVE |
Match is currently being played. |
MATCH_STATUS_TYPE_FINISHED |
Match has ended. |
MATCH_STATUS_TYPE_CANCELED |
Match has been cancelled. |
MATCH_STATUS_TYPE_PLACEHOLDER |
Placeholder entry; the match exists in our system but its participants are not yet confirmed. |
DataStatus
| Value | Description |
DATA_STATUS_UNSPECIFIED |
Default value. |
DATA_STATUS_DISABLED_DATA |
The underlying data source has been disabled. |
DATA_STATUS_CORRUPTED_DATA |
The data is known to be incorrect; treat it with caution. |
DATA_STATUS_VALID_DATA |
The data is healthy and can be trusted. |
3.6. Player URN format variants
A Player URN is the unique identifier of an individual player (for example, the player_urn property of CS2PlayerMapState) inside the match state. It is central to maintaining a correct match state. The URN is a string made up of three parts separated by colons. The parts and the three URN variants are described below.
- The first part is always the literal string "od", which is the Oddin.gg system prefix.
- The second part identifies which variant of the URN you have:
| Variant | Second part | Description |
| Internal ID | player |
Unique identifier of a player who exists in the Oddin.gg database and therefore has an internal ID. |
| External ID | player-ext |
Unique identifier of a player who has no internal ID assigned, but whose external ID is known from a data source (for example, a Steam ID). |
| Slot ID | player-slot |
A globally non-unique identifier for a player who has no internal ID and no known external ID. Within a single match, each player has a unique slot ID (for example, 1–10 for CS2), but the same slot ID may refer to a different player in another match. |
- The third part is the player-specific identifier, formatted as follows:
| Variant | Third part format | Example |
| Internal ID | number |
7811 |
| External ID | arbitrary string |
76561197960287930, "<arbitrary-string>", UUID, etc. |
| Slot ID | slot number |
1 |
Examples of possible player URN variants
| Variant | Example |
| Internal ID | od:player:7811 |
| External ID | od:player-ext:76561197960287930 |
| Slot ID | od:player-slot:1 |
4. Match events
In addition to LiveDataFeed, we provide a separate MatchEventsFeed endpoint that streams real-time game events for currently played matches.
4.1. Consuming the feed
To open the match-events stream, the client calls the MatchEventsFeed RPC and sends a MatchEventsFeedRequest.
Once connected, the server begins streaming MatchEventsFeedMessage frames for each active match. The first message for a match is a snapshot of all previous events; later messages each carry a single new event as it happens.
MatchEventsFeedRequest
| Field | Type | Description |
(no fields) |
— |
The request takes no parameters. |
4.2. MatchEventsFeedMessage
MatchEventsFeedMessage is the wrapping envelope for every match event frame. The payload is a oneof.
| Field | Type | Description |
cs2 |
CS2MatchEvents |
An array of CS2 events for a particular match. |
4.2.1. CS2MatchEvents
The first frame for a match contains a snapshot of every historical event for that match. Every later frame carries a single new event in real time.
| Property | Type | Description |
match_urn |
string |
Identifies the match. |
events |
repeated CS2Event |
List of events. On the initial frame this contains the full history; on later frames it contains a single new event. |
4.2.1.1. CS2Event
A CS2Event records what happened (its event payload), when it happened (its timestamp), and which map it relates to (its map_id).
| Property | Type | Description |
timestamp |
google.protobuf.Timestamp |
When the event was triggered. |
map_id |
uint64 |
Identifies the map within the match. |
event |
oneof (see event types table) |
One of the CS2 event payload types. See the next section for the supported list. |
4.2.1.2. CS2 event types
Only the events listed below are produced for CS2 matches today. The list will grow as we support more event types.
| Event | Message type | Example |
map_start |
CS2MapStart |
Map {X} started |
map_end |
CS2MapEnd |
Map {X} ended — Winner: Team A ({X:X}) |
round_start |
CS2RoundStart |
Round {X} started |
round_end |
CS2RoundEnd |
Round {X} ended (elimination) — Winner: Team B |
round_pause |
CS2RoundPause |
Round {X} paused |
round_resume |
CS2RoundResume |
Round {X} resumed |
round_rollback |
CS2RoundRollback |
Round {X} rolled back; next round is {Y} |
world_bomb |
CS2WorldBomb |
The bomb exploded |
bomb_planted |
CS2BombPlanted |
Player A has planted the bomb |
bomb_plant_started |
CS2BombPlantStarted |
Player A has started planting the bomb |
bomb_defuse_started |
CS2BombDefuseStarted |
Player B has started defusing the bomb |
bomb_defused |
CS2BombDefused |
Player B has defused the bomb |
bomb_exploded |
CS2BombExploded |
The bomb has exploded |
kill |
CS2Kill |
Player A killed Player B |
death |
CS2Death |
Player A was killed |
damage_dealt |
CS2DamageDealt |
Player A dealt damage to Player B |
freeze_time_started |
CS2FreezeTimeStarted |
Freeze time started |
freeze_time_ended |
CS2FreezeTimeEnded |
Freeze time ended |
item_drop |
CS2ItemDrop |
Player A dropped item |
item_pick_up |
CS2ItemPickUp |
Player A picked up item |
item_purchase |
CS2ItemPurchase |
Player A bought item |
item_throw |
CS2ItemThrow |
Player A threw Smoke Grenade |
Example JSON — CS2MatchEvents (initial frame, then a real-time update)
// Initial frame: snapshot of historical events
{
"cs2": {
"match_urn": "od:match:55501",
"events": [
{
"timestamp": "2026-05-27T18:00:05.000Z",
"map_id": 1,
"map_start": { "map_name": "de_mirage" }
},
{
"timestamp": "2026-05-27T18:00:20.000Z",
"map_id": 1,
"round_start": {
"time_remaining": "115s",
"current_round_number": 1
}
}
]
}
}
// Real-time frame: a single new event
{
"cs2": {
"match_urn": "od:match:55501",
"events": [
{
"timestamp": "2026-05-27T18:02:42.500Z",
"map_id": 1,
"kill": {
"time_remaining": "67s",
"current_round_number": 3,
"killer_position": { "x": 102.3, "y": -45.8, "z": 12.1, "angle": 87.0 },
"killer_urn": "od:player:7811",
"killer_team_urn": "od:team:101",
"victim_urn": "od:player-ext:76561197960287930",
"victim_team_urn": "od:team:202",
"weapon": "ak47",
"headshot": true,
"penetrated": false,
"assistants": []
}
}
]
}
}
5. Team profile
The TeamProfile RPC returns full, up-to-date information for a team identified by its URN. The response includes team metadata, external identifiers, every player on file for the team, and the players currently set as the map roster. It is a unary (request/response) call.
5.1. TeamProfileRequest
| Field | Type | Description |
team_urn |
string |
Unique identifier of the team, for example od:competitor:12345. |
5.2. TeamProfileResponse
| Field | Type | Description |
team |
TeamProfile |
The team's profile. |
5.3.1. TeamProfile
Full profile for a team.
| Field | Type | Description |
urn |
string |
Unique team identifier, for example od:competitor:12345. |
name |
string |
Team display name. |
icon_path |
string (optional) |
Path to the team's icon image. |
external_identities |
map<string, ExternalIdentityList> |
Key-value pairs mapping external identity providers to their corresponding team identifiers. For example: {"LIQUIPEDIA": ["456", "789"]}. |
players |
repeated PlayerProfile |
Every player known to be associated with the team. |
current_map_roster |
repeated PlayerProfile |
Players currently in the team's active roster for the current map. |
5.3.1.1. PlayerProfile
Profile for an individual player.
| Field | Type | Description |
urn |
string |
Unique player identifier, for example od:player:123. |
nickname |
string |
Player's in-game nickname. |
real_name |
string (optional) |
Player's real name. |
shortcut |
string (optional) |
Short abbreviation for the player. |
icon_path |
string (optional) |
Path to the player's icon image. |
age |
uint32 (optional) |
Player's age. |
external_identities |
map<string, ExternalIdentityList> |
Key-value pairs mapping external identity providers to their corresponding player identifiers. For example: {"LIQUIPEDIA": ["456", "789"]}. |
5.3.1.1.1. ExternalIdentityList
Wrapper for the list of external identifiers associated with a single provider.
| Field | Type | Description |
ids |
repeated string |
List of identifiers for the provider. |
Example JSON — TeamProfileRequest and TeamProfileResponse
// TeamProfileRequest
{
"team_urn": "od:competitor:12345"
}
// TeamProfileResponse
{
"team": {
"urn": "od:competitor:12345",
"name": "Team Alpha",
"icon_path": "icons/team_alpha.png",
"external_identities": {
"LIQUIPEDIA": { "ids": ["456", "789"] },
"STEAM": { "ids": ["76561198000000001"] }
},
"players": [
{
"urn": "od:player:7811",
"nickname": "AlphaStar",
"real_name": "Alex Smith",
"shortcut": "AST",
"icon_path": "icons/player_7811.png",
"age": 22,
"external_identities": {
"LIQUIPEDIA": { "ids": ["101"] }
}
}
],
"current_map_roster": [
{
"urn": "od:player:7811",
"nickname": "AlphaStar"
}
]
}
}
6. Game-specific schemas
Every game has its own message and state schema. Each is documented in its own article:
- Oddin.gg Bragi - Game-specific schemas: Counter-Strike 2
- Oddin.gg Bragi - Game-specific schemas: Dota 2
- Oddin.gg Bragi - Game-specific schemas: League of Legends
- Oddin.gg Bragi - Game-specific schemas: Valorant
- Oddin.gg Bragi - Game-specific schemas: eBasketball
- Oddin.gg Bragi - Game-specific schemas: eFootball
- Oddin.gg Bragi - Game-specific schemas: eCricket
7. Content updates
This section lists changes to the documentation and the underlying schema, newest first.
[v0.2.30] - 2026-05-29
- Added SPORT_CS2_DUELS to Sport enum.
- Added extended tables and JSON examples to the documentation.
[v0.2.29] - 2026-05-29
- Added SPORT_RUSH_CRICKET to Sport enum.
[v0.2.28] - 2026-05-20
- Added optional roshan_kills field to Dota 2 team map state.
[v0.2.27] - 2026-05-07
- Reverted the Roshans' logic.
[v0.2.26] - 2026-05-04
- Added organizer info into the Bragi API.
[v0.2.25] - 2026-05-01
- Added active_data_source_type to MatchMessage.
[v0.2.24] - 2026-04-08
- Added Roshans’ logic.
[v0.2.23] - 2026-03-12
- Added multiple external identity sources on the team profile.
[v0.2.22] - 2026-02-27
- Added team profile.
[v0.2.21] - 2026-02-25
- Added player external ID to Live Data Feed.
[v0.2.20] - 2026-02-12
- Added BallPossession field to Rush Basketball map state and partial updates.
[v0.2.19] - 2025-12-09
- Added start_date field to tournaments.
[v0.2.18] - 2025-11-20
- Added Match timeline filtering by team name.
[v0.2.17] - 2025-11-20
- Added Match timeline filtering.
[v0.2.16] - 2025-08-19
- Undeprecated PicksAndBansSelections. The schema now guarantees correct sequencing of picks and bans regardless of connection time and independent of any disruptions in data collection.
[v0.2.15] - 2025-06-05
- Added tournament online/offline flag on LiveDataFeed, MatchTimeline and MatchTimelineFeed.
[v0.2.14] - 2025-05-23
- Live eCricket support.
[v0.2.13] - 2025-04-09
- Added MatchEventsFeed to stream real-time match events. Currently only CS2 is supported.
[v0.2.12] - 2025-04-04
- Added the representation of picks and bans per team.
- Deprecated the previous picks and bans implementation.
[v0.2.11] - 2025-03-27
- Added the parameters timestamp and map_id for CSEvents.
- Added a separate CS2WorldBomb.
[v0.2.10] - 2025-03-06
- Undeprecated CS2 events.
- Added a few attributes.
[v0.2.9] - 2025-01-22
- Added the Roshan position on the minimap.
[v0.2.7] - 2025-01-22
- Added Dota 2 picks and bans ended boolean.
[v0.2.5] - 2024-12-16
- Dota 2 picks and bans were added to Dota2MatchSnapshot and Dota2MatchUpdate, in both the match state and the partial updates, as shown in the protobuf schema.