Skip to content
  • There are no suggestions because the search field is empty.

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 environment
  • main - 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
}
)