Oddin.gg Disir - Widgets documentation
Introduction
Oddin.gg’s Esports widgets, Disir, are the ultimate analytical companion to the odds feed. They bring together engaging facts and statistics meant to drive up interest in markets, by encouraging bettors to theorise, speculate and calculate around each individual match, while also acting as a one-stop solution for all relevant tournament information: fixtures, schedules, standings, and rosters.
Disir features all the depth and richness of established, community-favoured stats and news websites like hltv.org, dotabuff.com, or lol.fandom.com, but is integrated close to the odds, without users ever having to leave your website.
Data quality and freshness are ensured - we process raw data sources closest to the game publishers and update the data as soon as match replays are available.
Currently, Widgets come in the Prematch bundle featuring historical statistics for the two match competitors (with components: Team stats, Player stats, and Tournament stats), and as Live widgets, featuring real time data for the ongoing map. The Team stats, Player stats, and Tournament stats are all collected under the match widget endpoint (/statistics/match/:id), while the Tournament stats component can also be loaded separately by accessing the tournament widget endpoint (/statistics/tournament/:id). The live scoreboard Widget is available at the /live/scoreboard endpoint.
1. Supported Esports and eSimulators
| Esport / eSimulator | Live Widgets | Prematch Widgets |
| Counter-Strike 2 | ✅ | ✅ |
| League of Legends | ✅ | ✅ |
| Dota 2 | ✅ | ✅ |
| Valorant | ✅ | ✅ |
| eFootball | ✅ | ✅ |
| eBasketball | ✅ | ✅ |
| eCricket | ✅ | ❌ |
2. iFrame integration
Widgets behave as iFrame elements within HTML. We created a REST API that will prepare URL addresses for each widget - the returned URL address is used as src in the iFrame.
| Item | Description |
| REST API URL | https://api-disir.oddin.gg |
2.1. Usage
Call the REST API from your backend to receive the URL address of the widget. The address from the API response (JSON format) should then be used as src value in the iFrame element. Subscribe to the iFrame events to resize the iFrame and eliminate the scrollbar in the widget.
ℹ The match widget URL itself will not expire. However, in the case of older matches, it might not load anything if one of the teams has stopped being active (~3 month time-frame).
2.2. Domain whitelisting
Every domain where the Widgets will be running needs to be whitelisted on the Oddin.gg side, including the domains used for staging/integration purposes. At the start of the Widgets integration process, you should list all such domain URLs and send them to Oddin.gg integration support.
2.3. Recommended width
The minimum recommended width for both live and prematch widgets is at least 360px, with a minimal "usable" at 320px. Beyond this, readability cannot be ensured.
2.4. Referrer policy
For maximum compatibility with all possible Widget integrations (mobile, web…), we recommend to add the referrerpolicy attribute with the value "no-referrer-when-downgrade" to your iFrame element displaying the Widgets:
<iframe src="..." referrerpolicy="no-referrer-when-downgrade" ...></iframe>
3. REST API
This chapter provides a complete reference to the REST API. Here you will find the description of all methods and other necessary information required to interact with the REST API.
Additional parameters:
- All responses use JSON format.
- All requests are without body.
3.1. Authentication
The REST API is secured via a brand token. You should receive your brand token from our support. If the token is not working, or you still need to obtain it, please contact us at support@oddin.gg. Please note that you need a different access token for each environment, and then you need to supply the access token in each request in the header - “x-brand-token”.
3.2. Available endpoints
The REST API offers the following endpoints:
| HTTP Method | Path | Description |
| GET | /statistics/<env>/match/<match_id> |
Returns the URL for the match widget; <match_id> needs to be in the URN format obtained from the Odds Feed API, for example: od:match:1234. |
| GET | /statistics/<env>/tournament/<tournament_id> |
Returns the URL for the tournament widget; <tournament_id> needs to be in the URN format obtained from the Odds Feed API, for example: od:tournament:1234. |
| GET | /live/<env>/scoreboard/<match_id> |
Returns the URL for the live scoreboard widget; <match_id> needs to be in the URN format obtained from the Odds Feed API, for example: od:match:1234. |
<env> takes the values:
integration- for the Integration environmentmain- for the Production environment.
We currently provide two different environments - Production and Integration. The Production environment is intended to be used only when you are finished with integration and ready to go live. Please use the Integration environment for all development and testing purposes. You can select the environment by providing a different parameter in the URL for the REST calls.
3.2.1. Match endpoint
3.2.1.1. Query parameters
| Name | Required | Type | Default | Description |
| timeframe | no | string | “THREE_MONTHS” |”TWO_MONTHS” | The time interval in which statistics are computed. Supported values for Esports (CS2, Dota2, LoL, Valorant): ”ONE_MONTH” | “THREE_MONTHS”. Supported values for eSims (eFootball, eBasketball): ”ONE_MONTH” | ”TWO_MONTHS” Requesting a timeframe supported only for Esports on an eSims match will result in an error, and vice versa. |
| tab | no | string | “teams” | “stats” | The default tab after loading. Supported values for Esports (CS2, Dota 2, LoL, Valorant): “teams” | “players” | “tournament”. The widget will, by default, hide empty tabs (where no data is available). In case one of the empty tabs was specifically requested in the query, the widget will default to the first available tab, following the order: “teams” → “players” → “tournament”. Supported values for eSims (eFootball, eBasketball): “stats” | “ranking”. The widget will default to the first available tab, following the order: “stats” → “ranking”. Requesting a tab supported only for Esports on an eSims match will result in an error, and vice versa. |
| allowClose | no | boolean | false | Shows the Close button in the top right corner. The Close button is sending the CLOSE event. Example usage is the widget as a modal window. |
| language | no | string | “en” | A 2-character ISO 639-1 language code. Falls back to “en” if the entered language is not supported. |
| theme | no | string | “dark” | Switch between the light, dark, or custom theme. The dark theme is used by default. Supported values: “light” | “dark” | “custom theme name” |
3.2.1.2. Response body
| Name | Required | Type | Description |
| url | yes | string | URL to be used as src in the iFrame element. |
3.2.2. Tournament endpoint
3.2.2.1. Query parameters
| Name | Required | Type | Description |
| allowClose | no | boolean | Shows the Close button in the top right corner. The Close button is sending the CLOSE event. Example usage: the widget as a modal window. |
| language | no | string | A 2-character ISO 639-1 language code. Fallback to “en” if the entered language is not supported. |
| theme | no | string | Switch between the light, dark, or custom theme. The dark theme is used by default. Supported values: “light” | “dark” | “custom theme name” |
3.2.2.2. Response body
| Name | Required | Type | Description |
| url | yes | string | URL to be used as src in the iFrame element. |
3.2.3. Live scoreboard endpoint
3.2.3.1. Query parameters
| Name | Required | Type | Default | Description |
| language | no | string | “en” | A 2-character ISO 639-1 language code. Falls back to “en” if the entered language is not supported. |
| theme | no | string | “dark” | Switch between the light, dark, or custom theme. The dark theme is used by default. Supported values: “light” | “dark” | “custom theme name” |
3.2.3.2. Response body
| Name | Required | Type | Description |
| url | yes | string | URL to be used as src in the iFrame element. |
3.3. Error response
The HTTP response code will be the same as in the JSON body.
| Name | Required | Type | Description |
| code | yes | integer | HTTP response code. |
| message | no | string | Additional information about the error. |
We use the standard HTTP response codes. Please check the next table for a description of statuses used:
| Code | Name | Description |
| 200 | OK | The request was successful. |
| 401 | Unauthorized | The brand token is missing or invalid, or has expired. |
| 404 | Not found | The statistics or scoreboard not found or are not available. |
| 405 | Invalid arguments | Invalid arguments in the REST call. |
| 500 | Internal server error | Unable to process the request. |
REST API response codes
4. Events
iFrame widgets are reporting events via the Window.postMessage() method.
4.1. Subscribe to events
You should listen to the message events on the window - the iFrame is sending serialised JSON objects with a type property.
TypeScript code example
function subscribeToEvents(
iframe: HTMLIFrameElement,
onMessage: (event: WidgetEvent) => void,
onError?: (err: Error) => void
) {
const handleMessage = (e: MessageEvent) => {
if (e.source !== iframe.contentWindow) {
return
}
try {
const message = JSON.parse(e.data)
if (message && typeof message.type === 'string') {
onMessage(message)
}
} catch (e) {
onError?.(e)
}
}
window.addEventListener('message', handleMessage)
return () => window.removeEventListener('message', handleMessage)
}
4.2. Available events
type EventLoaded = {
type: 'LOADED'
}
type EventDataAvailability = {
type: 'DATA'
available: boolean
}
type EventClose = {
type: 'CLOSE'
}
type EventResize = {
type: 'RESIZE'
height: number
}
type EventScrollTop = {
type: 'SCROLL_TOP'
}
type WidgetEvent = EventLoaded | EventClose | EventResize | EventScrollTop
| Type | Description |
| LOADED | Widget loaded. |
| DATA | Notification about data availability for live widgets: true is sent when Widgets are available for the event, false is sent only if the availability of the Widgets changes from true to false, i.e., the Widgets were initially available for the event, but were then disabled (due to data loss or technical issues). If the Widgets are not initially available for an event, the DATA notification will not be sent. In case you wish to skip the built-in skeleton loading animation and use your own custom loader for the prematch widgets, the DATA event can be used as a reliable notification that all widget data has loaded, and the widget can be displayed immediately. True is sent when 1) all data has loaded upon initialization, and 2) when switching between tabs, when the active tab data has fully loaded. False is sent: 1) when switching to a valid tab, before the data has fully loaded, and 2) when switching from a tab containing data to a tab without any data available. The False condition can thus be used to trigger your custom loader. If there is no prematch data for a given match, the DATA notification will not be sent by the prematch widget. |
| CLOSE | The “close widget” event. Sent after clicking the “close” button when allowClose is enabled for the widget. |
| RESIZE | The “resize widget” event. The iFrame height should be updated to match the widget height. |
| SCROLL_TOP | To synchronise scrolling between the iFrame content and its parent page, clients need to subscribe to the SCROLL_TOP event emitted by the iFrame. Upon receiving this event, the client’s application should handle scrolling of the parent page’s wrapper element accordingly. |
Events
4.3. Example use: prematch widget
const unsubscribe = subscribeToEvents(
iframeInstance,
(event) => {
switch (event.type) {
case 'RESIZE':
iframeInstance.style.height = `${event.height}px`
break
case 'CLOSE':
destroyInstance()
break
case 'LOADED':
setLoading(false)
break
case 'SCROLL_TOP':
syncScrolling()
break
}
},
(err) => {
// Error
}
)
4.4. Example use: live widget
The addresses for live widgets are generated even without data, so the frontend can load the widget in advance and wait for data availability. For the best user experience, we recommend hiding the widget initially, and then revealing it only when the data becomes available. The data can be lost during the match, sending the widget into an Empty state (“Live stats not available” screen). Clients have the option to display this screen, or the widget can be hidden until the data becomes available again.
iframeInstance.style.display = 'none'
const unsubscribe = subscribeToEvents(
iframeInstance,
(event) => {
switch (event.type) {
case 'RESIZE':
iframeInstance.style.height = `${event.height}px`
break
case 'DATA':
iframeInstance
.style.display = event.available ? 'block' : 'none'
break
}
},
(err) => {
// Error
}
)