Betradar UOF HTTP API

Copy Markdown View Source

CI Package Version hexdocs.pm

[!IMPORTANT] This is an unofficial client for Betradar's Unified Odds Feed (UOF) HTTP API. Betradar offers official Java and .NET SDKs. You can read more about these here.

Elixir client for Betradar's Unified Odds Feed (UOF) HTTP API.

It covers the documented HTTP API surface across these modules:

ModuleAPI
UOF.API.SportsSchedules and fixtures, fixture/result changes, summaries, timelines, sports, tournaments, seasons, categories, and player/competitor/venue profiles
UOF.API.DescriptionsBetting descriptions: markets, variants, producers, match/betting statuses, betstop/void reasons
UOF.API.ProbabilitySport-event probabilities (cashout)
UOF.API.CustomBetAvailable selections and odds/probability calculation
UOF.API.RecoveryOdds and stateful-message recovery
UOF.API.BookingBooking events for live odds
UOF.API.UsersToken / bookmaker information

Responses are parsed into Ecto embedded schemas from the uof_schemas package.

Get Started

Configuration

To be able to interact with Betradar's API, you first need to configure a valid authentication token and the base url of the Betradar environment you want to use.

:ok = Application.put_env(:uof_api, :base_url, "<betradar-uof-base-url>")
:ok = Application.put_env(:uof_api, :auth_token, "<betradar-uof-auth-token>")

Optionally, configure :req_options — a keyword list merged into every request (via Req.merge/2), for things like timeouts and retries:

:ok = Application.put_env(:uof_api, :req_options, receive_timeout: 10_000, retry: :transient)

Every API function also accepts a trailing opts keyword list to override request behavior for a single call, without touching the global config:

UOF.API.Sports.fixture("sr:match:12345", "en", receive_timeout: 30_000)

Usage

Stream all available fixtures. Pagination over the prematch schedule is handled for you, and pages are fetched lazily so the stream composes with Stream/Enum and supports early termination.

UOF.API.Sports.Fixtures.stream()
|> Enum.count()
# => 51591

Compose Stream operations to narrow the catalogue down without loading it all into memory — for example, all events currently bookable for live odds:

UOF.API.Sports.Fixtures.stream()
|> Stream.filter(&(&1.liveodds == "bookable"))
|> Enum.to_list()

Aggregating over the streamed events works as you would expect:

UOF.API.Sports.Fixtures.stream()
|> Stream.map(& &1.tournament.sport.name)
|> Enum.uniq()
# => ["Soccer", "Handball", "Basketball", ...]

Fetch the details of a single fixture:

{:ok, %{fixture: fixture}} = UOF.API.Sports.fixture("sr:match:8696826")
# =>
# %UOF.Schemas.API.Sports.Fixture{
#   id: "sr:match:8696826",
#   status: "closed",
#   liveodds: "not_available",
#   scheduled: ~U[2016-10-31 18:00:00Z],
#   tournament: %UOF.Schemas.API.Sports.Tournament{
#     name: "Ettan, Sodra",
#     sport: %UOF.Schemas.API.Sports.Sport{name: "Soccer", ...},
#     ...
#   },
#   season: %UOF.Schemas.API.Sports.SeasonExtended{
#     name: "Div 1, Sodra 2016",
#     start_date: ~D[2016-04-16],
#     ...
#   },
#   competitors: %UOF.Schemas.API.Sports.SportEventCompetitors{
#     competitor: [
#       %UOF.Schemas.API.Sports.TeamCompetitor{id: "sr:competitor:1860", name: "IK Oddevold", qualifier: "home", ...},
#       %UOF.Schemas.API.Sports.TeamCompetitor{id: "sr:competitor:22356", name: "Tvaakers IF", qualifier: "away", ...}
#     ]
#   },
#   ...
# }

Fetch an entity profile (note the faithful nesting — jerseys.jersey, players.player):

{:ok, profile} = UOF.API.Sports.competitor("sr:competitor:2672")
# =>
# %UOF.Schemas.API.Sports.CompetitorProfileEndpoint{
#   competitor: %UOF.Schemas.API.Sports.TeamExtended{name: "Bayern Munich", ...},
#   venue:   %UOF.Schemas.API.Sports.Venue{name: "Allianz Arena", capacity: 75000, ...},
#   jerseys: %UOF.Schemas.API.Sports.Jerseys{jersey: [...]},   # 4
#   players: %UOF.Schemas.API.Sports.Players{player: [...]},   # 32
#   ...
# }

Betting descriptions:

{:ok, %{market: markets}} = UOF.API.Descriptions.markets()
{:ok, %{producer: producers}} = UOF.API.Descriptions.producers()

Custom bet lets you combine markets across fixtures into a single accumulator. First list the markets available for a fixture:

{:ok, available} = UOF.API.CustomBet.available_selections("sr:match:42795059")

market = hd(available.markets)
outcome = hd(market.outcomes)
# a selection is a {fixture_id, market_id, outcome_id} tuple
{"sr:match:42795059", market.id, outcome.id}

Then calculate the combined odds and probability for a list of selections:

{:ok, calculation} = UOF.API.CustomBet.calculate([
  {"sr:match:42795059", 97, 74},
  {"sr:match:42795059", 10, 9}
])
# calculation.calculation.odds        => #Decimal<5.22>
# calculation.calculation.probability => #Decimal<0.15>

Pass true as the second argument to calculate/2 to also get back the further markets that can still be added to the bet.

Probabilities (cashout):

{:ok, cashout} = UOF.API.Probability.probabilities("sr:match:41878167")

Booking. There are no booking-calendar GET endpoints; booking state is read from a schedule via the liveodds attribute:

{:ok, _ack} = UOF.API.Booking.book("sr:match:12345")

{:ok, schedule} = UOF.API.Sports.schedule("2024-12-01")
UOF.API.Sports.Fixtures.bookable(schedule)
# also: booked/1, buyable/1, with_liveodds/2

Odds recovery (the recovered messages arrive over the AMQP feed; the HTTP call returns an acknowledgement):

{:ok, _ack_or_nil} = UOF.API.Recovery.recover("liveodds", request_id: 1, after: 1_700_000_000_000)
{:ok, _ack_or_nil} = UOF.API.Recovery.recover_event("liveodds", "sr:match:12345", request_id: 2)

Error handling

API calls return {:ok, response} for every successful HTTP status and {:error, %UOF.API.Error{}} for HTTP, transport, or XML decoding failures. Successful responses without a body return {:ok, nil}.

The numeric HTTP status and Sportradar's response code are kept separately:

case UOF.API.Sports.fixture("sr:match:12345") do
  {:ok, fixture} ->
    fixture

  {:error, %UOF.API.Error{status: 404, response_code: "NOT_FOUND"} = error} ->
    error.message

  {:error, %UOF.API.Error{} = error} ->
    error
end

When present, response headers and the decoded error body are available through error.headers and error.body. Non-XML bodies such as JSON or CDN-generated HTML are preserved. Errors encountered by UOF.API.Sports.Fixtures.stream/1 are raised while the stream is enumerated.

Contributing

This project uses Conventional Commits.

The list of supported commit types can be found here.