Skip to main content

Bifrost API Reference

Source: BFST-Bifrost API Public Documentation (2026-03-06)


1. Sports Identifiers

Sport NameSport ID
FOOTBALL1
CRICKET2
KABADDI19

2. Queue Architecture

All queue names are prefixed with {customerPrefix} — the customer's unique prefix assigned by Bifrost.

2.1 Outbound Queues (from Bifrost to customer)

Queue NameProducesDescription
{customerPrefix}.category.queueCategoryMessages containing category (Country or Competition) details.
{customerPrefix}.event.queueEventMessages containing full event details. Upon arrival, it should fully override the existing snapshot after version check.
{customerPrefix}.market-catalogue.queueMarketCatalogueMessages containing full market static details. Upon arrival, it should override the existing snapshot after the version check.
{customerPrefix}.market-book.queueMarketBookMessages containing full market dynamic details. Upon arrival, it should override the existing snapshot after the version check.

2.2 Broker Data Queues (bet lifecycle)

Bets (single bets)

Queue NameProducesDescription
{customerPrefix}.bets.snapshot.queueBetSnapshotMessages containing the Bet holding the current state of the bet. Covers: acceptance, rejections, voiding, size matching and cancellation. Upon arrival, fully override the existing snapshot after the version check.
{customerPrefix}.bets.cancel.queueBetSnapshotMessages containing cancelled bet. Upon arrival, should fully override existing snapshot after version check.

Multibets

Queue NameProducesDescription
{customerPrefix}.bets.multi-bet.snapshot.queueMultiBetSnapshotMessages containing the Multibet holding the current state of the bet. Covers: acceptance, rejections, voiding. Upon arrival, fully override the existing snapshot after the version check.
{customerPrefix}.bets.multi-bet.outcomes.queueMultiBetOutcomeMessages containing full bet outcome snapshot. Upon arrival, should fully override existing snapshot after version check.
{customerPrefix}.bets.multi-bet.price.queueMultibetPriceBetSnapshotMessages containing multibet price response.

Bet Outcomes (settlement)

Queue NameProducesDescription
{customerPrefix}.bets.outcomes.queueBetOutcomeMessages containing full bet outcome snapshot. Upon arrival, should fully override existing snapshot after version check.

3. Outbound Data Structures

3.1 Category

Queue: {customerPrefix}.category.queue

Routing patterns:

  • customerPrefix — customer unique prefix

Model

FieldData TypeDescription
idstringCategory id
parentIdstringLink to parent. Category should be treated as a hierarchy.
categoryTypestringType of category. e.g. 'Country' or 'Competition'
valuestringName of category. e.g. Country (England), Competition (Premier League)
sportIdstringSport Id

Proto

message Category {
string id = 1;
string parentId = 2;
string categoryType = 3;
string value = 4;
string sportId = 5;
}

3.2 Event

Queue: {customerPrefix}.event.queue

Routing patterns:

  • customerPrefix — customer unique prefix

Upon arrival, it should fully override the existing snapshot after version check.

Event Model

FieldData TypeDescription
eventIdstringA unique id for the event.
namestringA display name for the event.
startTimestringThe event start time in UTC zone. E.g. '2025-05-06T18:00'
sportIdstringSport id
categoryIdstringThe category/competition reference
competitorsCompetitor[]A list of competitors that are involved in this event.
externalIdsEventMapping[]A possibly empty list of other providers event ids. The list may change at any time as no mapping should be assumed as static nor unique. Example providers: Betfair, Sportradar.
statusstringEvent status enum. One of: CANCELLED — the game was cancelled; POSTPONED — the game was postponed.
versionlongVersion of event. This can be used for deduplication and ordering of updates downstream.

Event Proto

message Event {
string eventId = 1;
string name = 2;
string startTime = 3;
string categoryId = 4;
repeated Competitor competitors = 5;
repeated EventMapping externalIds = 6;
string status = 7;
int64 version = 8;
string sportId = 9;
}

EventMapping Model

FieldData TypeDescription
providerstringProvider of the id e.g. Betfair
eventIdstringThe id of the event for the provider.

EventMapping Proto

message EventMapping {
string provider = 1;
string eventId = 2;
}

Note: the Event proto has no parent field — Bifrost events are flat and first-class. For how Hannibal resolves market ownership, orphaned markets, and the servability invariant, see §6 → Event Model — corrected 2026-07-06.


3.3 Competitor / Team

Model

FieldData TypeDescription
idstringCompetitor id
shortNamestringCompetitor short name. e.g. 'ENG'
fullNamestringCompetitor full name. e.g. 'England'

Proto

message Competitor {
string id = 1;
string shortName = 2;
string fullName = 3;
}

3.4 MarketCatalogue

Queue: {customerPrefix}.market-catalogue.queue

Routing patterns:

  • customerPrefix — customer unique prefix

Messages containing full market static details. Upon arrival, it should override the existing snapshot after the version check.

MarketCatalogue Model

FieldData TypeDescription
idstringThe unique id of the market.
eventIdstringThe id of the event.
marketTypestringThe market type.
oddsTypestringOdds type.
namestringThe market name.
startTimestringThe market start time.
prematchOnlybooleanBoolean flag indicating whether the market will be open during prematch only or will go live on game start.
sortOrderlongRecommended sort order of the market.
runnersRunnerCatalogue[]The set of betable selections on the market.
metadataMarketMetadataThe market metadata.
versionlongVersion of market. This can be used for deduplication and ordering of updates downstream.
sportIdstringThe id of the sport.
tradingMarketTypestringOriginal market type (from Bifrost trading system internals).

MarketCatalogue Proto

message MarketCatalogue {
string id = 1;
string eventId = 2;
string marketType = 3;
string name = 4;
string startTime = 5;
string oddsType = 6;
bool prematchOnly = 7;
int64 sortOrder = 8;
repeated RunnerCatalogue runner = 9;
MarketMetadata metadata = 10;
int64 version = 11;
string sportId = 12;
string tradingMarketType = 13;
}

RunnerCatalogue Model

FieldData TypeDescription
idlongThe id of the runner.
namestringThe name of the runner.
sortPriorityintegerThe recommended positional order of the selection when being displayed.
metadatamap<String, String>The metadata of the runner.

RunnerCatalogue Proto

message RunnerCatalogue {
int64 id = 1;
string name = 2;
int32 sortPriority = 3;
map<string, string> metadata = 4;
}

MarketMetadata Model

FieldData TypeDescription
competitorsMarketCompetitorMetadataMarket competitors metadata.

MarketMetadata Proto

message MarketMetadata {
repeated MarketCompetitorMetadata competitor = 1;
}

MarketCompetitorMetadata Model

FieldData TypeDescription
namestringMarket competitor full name.
shortNamestringMarket competitor short name.

MarketCompetitorMetadata Proto

message MarketCompetitorMetadata {
string name = 1;
string shortName = 2;
}

3.5 MarketBook

Queue: {customerPrefix}.market-book.queue

Routing patterns:

  • customerPrefix — customer unique prefix

Messages containing full market dynamic details. Upon arrival, it should override the existing snapshot after the version check.

MarketBook Model

FieldData TypeDescription
marketIdstringThe unique id of the market.
marketStatusstringThe status of the market. One of: INACTIVE, OPEN, BALL_RUNNING, SUSPENDED, CLOSED, SETTLED. Note: BALL_RUNNING is currently supported but is planned for deprecation.
maxMarketdoubleMax market liability per b2b client member in HKD.
maxMarketCurrenciesmap<string, double>Max market liability per b2b client member in provided currencies.
runnersRunner[]The set of betable selections on the market.
variablesmap<string, string>The market variables.
versionLongThe version of market. This can be used for deduplication and ordering of updates downstream.
sportIdstringThe id of the sport.
minStakedouble (optional)Minimum stake.
minStakeCurrenciesmap<string, double>Minimum stake in provided currencies.
tradingMarketTypestringOriginal market type (from Bifrost trading system internals).
oddsTypestringThe odds type, one of: HAAR_JEET, DECIMAL. Authoritative pricing format — use this field (not MarketCatalogue.oddsType) to determine how to interpret runner prices.

marketStatus Values

ValueMeaning
INACTIVEMarket is not available for bet placement.
OPENMarket is available for bet placement.
BALL_RUNNINGMarket is currently in ball-running state. Currently supported but planned for deprecation.
SUSPENDEDMarket is not available for bet placement.
CLOSEDMarket is not available for bet placement.
SETTLEDMarket is fully settled.

MarketBook Proto

message MarketBook {
string marketId = 1;
string marketStatus = 2;
double maxMarket = 3;
map<string, double> maxMarketCurrencies = 4;
repeated Runner runner = 5;
map<string, string> variables = 6;
int64 version = 7;
string sportId = 8;
optional double minStake = 9;
map<string, double> minStakeCurrencies = 10;
string tradingMarketType = 11;
string oddsType = 12;
}

3.6 Runner / RunnerPriceLadder / RunnerPriceSize

Runner Model

FieldData TypeDescription
idstringThe id of the runner that should be provided when placing a bet.
statusstringThe bettable status of the runner.
priceLadderRunnerPriceLadderThe prices ladder of the runner.
versionlongThe runner version.

Runner Proto

message Runner {
int64 id = 1;
string status = 2;
RunnerPriceLadder priceLadder = 3;
int64 version = 4;
}

RunnerPriceLadder Model

FieldData TypeDescription
availableToBackRunnerPriceSize[]The available BACK prices and available max stake to bet at that price.
availableToLayRunnerPriceSize[]The available LAY prices and available max stake to bet at that price. Only populated for exchange markets.

RunnerPriceLadder Proto

message RunnerPriceLadder {
repeated RunnerPriceSize availableToBack = 1;
repeated RunnerPriceSize availableToLay = 2;
}

RunnerPriceSize Model

FieldData TypeDescription
pricedoubleThe price at which this runner can be bettable by customer.
midPricedoubleThe mid price.
sizedouble(field 3 in proto)
linedoubleThe runner line.
midLinedoubleThe runner mid line.
priceIndexintegerThe position order of the line.
maxStakedoubleThe maximum stake size of a bet at this price.
maxStakeCurrenciesmap<string, double>The maximum stake size of a bet at this price in provided currencies.

RunnerPriceSize Proto

message RunnerPriceSize {
double price = 1;
double midPrice = 2;
double size = 3;
double line = 4;
double midLine = 5;
int32 priceIndex = 6;
double maxStake = 7;
map<string, double> maxStakeCurrencies = 8;
}

3.7 FootballStatistics

Note: Football-specific live statistics message.

FootballStatistics Model

FieldTypeDescription
eventIdStringUnique identifier for the event
matchPhaseStringCurrent phase of the match
phaseMinuteIntegerMinute within the current phase
homeTeamNameStringName of the home team
awayTeamNameStringName of the away team
goalsHalfTimeStateStatisticGoals scored by half-time
goalsFullTimeStateStatisticGoals scored by full-time
goalsOvertimeHalfTimeStateStatisticGoals scored by overtime half-time
goalsOvertimeFullTimeStateStatisticGoals scored by overtime full-time
goalsPenaltyKicksStateStatisticGoals scored by penalty kicks
cornersHalfTimeStateStatisticCorners taken by half-time
cornersFullTimeStateStatisticCorners taken by full-time
cornersOvertimeHalfTimeStateStatisticCorners taken by overtime half-time
cornersOvertimeFullTimeStateStatisticCorners taken by overtime full-time
yellowCardsHalfTimeStateStatisticYellow cards by half-time
yellowCardsFullTimeStateStatisticYellow cards by full-time
yellowCardsOvertimeHalfTimeStateStatisticYellow cards by overtime half-time
yellowCardsOvertimeFullTimeStateStatisticYellow cards by overtime full-time
redCardsHalfTimeStateStatisticRed cards by half-time
redCardsFullTimeStateStatisticRed cards by full-time
redCardsOvertimeHalfTimeStateStatisticRed cards by overtime half-time
redCardsOvertimeFullTimeStateStatisticRed cards by overtime full-time

FootballStatistics Proto

message FootballStatistics {
string eventId = 1;
string matchPhase = 3;
int32 phaseMinute = 4;
string homeTeamName = 5;
string awayTeamName = 6;
StateStatistic goalsHalfTime = 7;
StateStatistic goalsFullTime = 8;
StateStatistic goalsOvertimeHalfTime = 9;
StateStatistic goalsOvertimeFullTime = 10;
StateStatistic goalsPenaltyKicks = 11;
StateStatistic cornersHalfTime = 12;
StateStatistic cornersFullTime = 13;
StateStatistic cornersOvertimeHalfTime = 14;
StateStatistic cornersOvertimeFullTime = 15;
StateStatistic yellowCardsHalfTime = 16;
StateStatistic yellowCardsFullTime = 17;
StateStatistic yellowCardsOvertimeHalfTime = 18;
StateStatistic yellowCardsOvertimeFullTime = 19;
StateStatistic redCardsHalfTime = 20;
StateStatistic redCardsFullTime = 21;
StateStatistic redCardsOvertimeHalfTime = 22;
StateStatistic redCardsOvertimeFullTime = 23;
}

StateStatistic Model

FieldTypeDescription
homeStringStatistic for the home team
awayStringStatistic for the away team

StateStatistic Proto

message StateStatistic {
int32 home = 1;
int32 away = 2;
}

3.8 BetSnapshot

Queue: {customerPrefix}.bets.snapshot.queue

Covers: acceptance, rejections, voiding, size matching and cancellation.

BetSnapshot Model

FieldData TypeDescription
requestIdStringA request unique identifier. It could be generated on the customer side.
betIdLongUnique identifier for the placed bet. Generated on Public API level.
memberCodeStringCode identifying the member placing the bet.
statusStringBet status enum. One of: PENDING — the bet placement request has been sent, waiting for provider's response; FAILED — the bet placement has failed; PLACED — the bet was successfully placed (fully matched); VOIDED — the bet was voided by the provider/operator; PARTIALLY_MATCHED — part of the bet has been matched, the rest is waiting in the market; UNMATCHED — the bet is in the market but has not been matched yet; CANCEL_PENDING — the client requested to cancel the bet (or remaining part), but the provider has not yet confirmed the cancellation; CANCELLED — the bet (or its unmatched portion) was canceled by the user; LAPSED — the bet automatically expired because the market closed or conditions were no longer valid.
sideStringSide enum. One of: BACK — selection occurrence side selection; LAY — selection non-occurrence side selection.
marketIdStringIdentifier for the specific market within an event.
runnerIdLongIdentifier for the specific runner within a market.
sizedoubleBet size requested for placement in customer currency.
sizeMatcheddoubleBet size matched at the time of bet request placement. Applicable for Exchange markets.
oddsdoubleAccepted bet odds.
lineintThe bet line, can be empty.
errorMessageStringMessage from a provider accompanying failed or voided bet; empty if a bet is placed.
errorCodeStringCode from a provider accompanying failed or voided bet; empty if a bet is placed.
versionlongTimestamp when the bet was placed (UTC).
sizeRemainingdoubleThe remaining unmatched portion of the bet that is still active in the market. Applicable for Exchange markets.
sizeLapseddoubleThe portion of the bet that has lapsed (i.e. was not matched before the market closed or the event started) and was automatically removed. Applicable for Exchange markets.
sizeCancelleddoubleThe portion of the bet that was cancelled by the user or system before being matched. Applicable for Exchange markets.
sizeVoideddoubleThe portion of the bet that was voided (e.g. due to event cancellation or market rule changes). Applicable for Exchange markets.
averageOddsdoubleThe weighted average odds at which the bet was matched. If the bet is partially matched at different odds, this value reflects the overall effective odds based on matched sizes. Applicable for Exchange markets.
voidReasonStringMessage from operator accompanying failed or voided bet.

Source-fidelity correction (2026-07-20): the original Bifrost PDF's BetSnapshot table has no Required column. An earlier local transcription added one and incorrectly marked the exchange buckets unconditionally required. The proto declares them as non-optional proto3 scalar doubles, which use implicit zero presence; that does not establish a requirement that every field tag be serialized for bookmaker markets.

BetSnapshot Proto

message BetSnapshot {
string requestId = 1;
int64 betId = 2;
string marketId = 3;
int64 runnerId = 4;
double size = 5;
optional double odds = 6;
optional int32 line = 7;
string side = 8;
string memberCode = 9;
string status = 10;
optional string errorMessage = 11;
optional string errorCode = 12;
int64 version = 13;
double sizeMatched = 14;
double sizeRemaining = 15;
double sizeLapsed = 16;
double sizeCancelled = 17;
double sizeVoided = 18;
double averageOdds = 19;
optional string voidReason = 20;
}

Forsyt execution contract

  • Successful placement HTTP responses are PENDING acknowledgements, not match evidence.
  • For 9.x/14.x, the order remains unmatched until a queue PLACED snapshot passes immutable identity, version, odds, and requested-size validation.
  • PLACED plus validated size confirms a full all-or-nothing match. Hannibal does not depend on the exchange Size Vector for that transition.
  • Missing sizeMatched is non-blocking telemetry. Present sizeMatched == size corroborates the status.
  • Partial sizeMatched, non-zero remaining evidence, PARTIALLY_MATCHED, or UNMATCHED contradicts the 9.x/14.x contract. No financial state is changed; the snapshot is dead-lettered and a deduplicated financial-integrity alert is emitted.
  • Before the first confirmed match, full FAILED, LAPSED, and CANCELLED snapshots release the unmatched order and atomically stop intent recovery. VOIDED is not placement rejection evidence; it remains on the separate settlement path and is accepted only after a confirmed match.
  • A post-send HTTP timeout is an ambiguous placement outcome. Hannibal freezes the exact original provider request in a ProviderPlacementIntent atomically with the liability reserve. Recovery requests only the authoritative snapshot by the original request ID; it does not resubmit the placement. The order and its full accepted liability remain unmatched/reserved, a deduplicated alert is emitted, and /api/v1/recovery/bets is requested with betRequestIds: [requestId]. Timeout or elapsed time alone never authorizes a refund. Placement resubmission remains disabled until Bifrost documents same-request, identical-payload idempotency.
  • The recovery response { requestId, error } acknowledges an asynchronous recovery request; it is not a bet-existence result. Neither an empty recovery error nor silence after recovery proves that no bet exists. The supplied Bifrost PDF/OpenAPI documents no authoritative negative-existence response, so unresolved ambiguity remains reserved and is escalated to operations.
  • The supplied OpenAPI models error as an optional string. At the JSON boundary Hannibal also accepts null as an explicitly absent error, but only when requestId is a nonblank string. Every other non-string value remains an invalid acknowledgement; a nonempty string remains a provider rejection.
  • Future 1.x exchange execution remains blocked for real-money placement. Until live exchange evidence proves an omission rule, Forsyt requires every absolute Size Vector bucket to be present on the wire and internally consistent; omitted financial buckets are not guessed as zero.

3.9 BetOutcomeSnapshot

Queue: {customerPrefix}.bets.outcomes.queue

Settlement information. Upon arrival, should fully override existing snapshot after version check.

BetOutcomeSnapshot Model

FieldData TypeDescription
requestIdString(from BetSnapshot.requestId)
betIdlongBet id.
outcomestringBet outcome enum. One of: WON — the bet has won; LOST — the bet has lost; VOID — the bet was voided.
pnldoubleBet profit and loss outcome.
versionLongTimestamp when the bet was settled (UTC).
sizeSettleddoubleProvider matched size in the case of a partial match.

BetOutcome Proto

message BetOutcomeSnapshot {
string requestId = 1;
int64 betId = 2;
string outcome = 3;
double pnl = 4;
int64 version = 5;
double sizeSettled = 6;
}

3.10 MultiBetSnapshot

Queue: {customerPrefix}.bets.multi-bet.snapshot.queue

Covers: acceptance, rejections, voiding.

MultiBetSnapshot Model

FieldData TypeRequiredDescription
requestIdStringYesA request unique identifier. It could be generated on the customer side.
betIdLongYesUnique identifier for the placed bet. Generated on Public API level.
statusStringYesBet status enum: PENDING, FAILED, PLACED, VOIDED.
errorMessageStringOptionalMessage from a provider accompanying failed or voided bet.
errorCodeStringOptionalCode accompanying failed or voided bet.
versionlongYesTimestamp when the bet was placed (UTC).
stakedoubleOptionalStake.
multibetPricedoubleOptionalMultibet price.
memberCodeStringYesCode identifying the member placing the bet.
voidReasonStringOptionalMessage from operator accompanying failed or voided bet.
multibetLegsList<MultibetLegSnapshot>YesList of multibet legs.

MultiBetSnapshot Proto

message MultiBetSnapshot {
string requestId = 1;
int64 betId = 2;
string status = 3;
optional string errorMessage = 4;
optional string errorCode = 5;
optional double stake = 6;
optional double multibetPrice = 7;
string memberCode = 8;
optional string voidReason = 9;
repeated MultibetLegSnapshot multibetLeg = 10;
int64 version = 11;
}

message MultibetLegSnapshot {
string eventId = 1;
string marketId = 2;
int64 selectionId = 3;
string side = 4;
double requestedOdds = 5;
double odds = 6;
}

MultibetLegSnapshot Model

FieldData TypeDescription
eventIdStringIdentifier for the specific event within a multibet event.
marketIdStringIdentifier for the specific market within a multibet event.
selectionIdLongIdentifier for the specific selection within a multibet event.
sideStringSide enum. BACK — selection occurrence side selection.
requestedOddsdoubleThe requested odds within a multibet event.
oddsdoubleThe accepted odds within a multibet event.
memberCodeStringCode identifying the member placing the bet.
voidReasonStringMessage from operator accompanying failed or voided bet.

3.11 MultiBetOutcomeSnapshot

Queue: {customerPrefix}.bets.multi-bet.outcomes.queue

MultiBetOutcomeSnapshot Model

FieldData TypeDescription
requestIdStringA request unique identifier. It could be generated on the customer side.
betIdlongBet id.
outcomestringBet outcome enum: WON, LOST, VOID.
pnldoubleBet profit and loss outcome.
versionLongTimestamp when the bet was settled (UTC).
sizeSettleddoubleProvider matched size in the case of a partial match.
voidReasonstringMessage from operator accompanying failed or voided bet.
unvoidReasonstringMessage from operator accompanying failed or unvoided bet.

MultiBetOutcome Proto

message MultiBetOutcomeSnapshot {
string requestId = 1;
int64 betId = 2;
string outcome = 3;
double pnl = 4;
int64 version = 5;
double sizeSettled = 6;
string voidReason = 7;
string unvoidReason = 8;
}

3.12 MultibetPriceSnapshot

Queue: {customerPrefix}.bets.multi-bet.price.queue

MultibetPriceSnapshot Model

FieldData TypeDescription
requestIdstringA request unique identifier.
marginedOddsdoubleMargined odds of multibet price request.
successbooleanStatus of the multibet price request.
errorMessagestringError message.

MultibetPriceSnapshot Proto

message MultiBetPriceSnapshot {
string requestId = 1;
double marginedOdds = 2;
string success = 3;
optional string errorMessage = 4;
}

4. REST Betting API

Base URL: Provided by Bifrost on customer onboarding.

Authentication: Hannibal sends BIFROST_API_TOKEN verbatim as the Authorization header. Configure it with the exact complete header value issued by Bifrost, including any scheme Bifrost provides; Hannibal neither prepends nor strips Bearer.

Content-Type: application/json


4.1 Place Bet

Endpoint: POST /api/v1/bets/place

Description: Allows the customer to place a new bet on an event.

Note: All successful bet responses will be returned with PENDING status. After the provider processes the bet placement, results (placed or not) will be provided in the RabbitMQ queue.

Request Headers

Authorization: YOUR_API_TOKEN
Content-Type: application/json

PlaceBetRequest Fields

FieldTypeRequiredDescription
sportIdStringYesSport identifier.
marketIdStringYesIdentifier for the specific market within an event.
runnerIdLongYesIdentifier for the specific runner within a market.
priceIndexIntegerNoThe index of the price level on the price ladder within the selection. For example, if a selection had prices 1.91 and 1.90 then a price_level_index of 0 would be 1.91 and 1 would be for 1.90.
sizeBigDecimalYesBet size requested for placement.
memberSizeBigDecimalYesBet size initially requested by the member (before PT).
oddsBigDecimalYesBet odds requested for placement.
lineIntegerNoBet line, for line fancy markets only.
sideSideYesSide enum. One of: BACK — selection occurrence side selection; LAY — selection non-occurrence side selection.
memberCodeStringYesCode identifying the member placing the bet.
ipAddressStringYesIP address of the client placing the bet.
currencyStringNoCurrency code (e.g., "USD").
requestIdStringNoA unique identifier that can be generated on the customer side for the request to ensure idempotency.

Request Body Example

{
"sportId": "2",
"marketId": "9.1223445",
"runnerId": 100123,
"priceIndex": 0,
"size": 50,
"memberSize": 50,
"odds": 100,
"line": 10,
"side": "BACK",
"memberCode": "member_code",
"currency": "HKD",
"ipAddress": "member_ip_address",
"requestId": "123456789"
}

PlaceBetResponse Fields

FieldTypeDescription
betIdLongUnique identifier for the placed bet.
requestIdStringA request unique identifier.
memberCodeStringCode identifying the member placing the bet.
statusBetStatusBet status enum: PENDING, FAILED, PLACED, VOIDED.
sideSideBACK or LAY.
sizeBigDecimalBet size requested for placement.
oddsBigDecimalBet odds requested for placement.
lineIntegerBet line requested for placement.
versionlongTimestamp when the bet was placed (UTC).

Response Example — Success (201 Created)

{
"id": 3,
"requestId": "123456789",
"memberCode": "member_code",
"status": "PENDING",
"side": "BACK",
"size": 5,
"memberSize": 5,
"odds": 100,
"line": 10,
"version": 1727702890748
}

PlaceBetErrorResponse Fields

FieldTypeDescription
errorCodeStringCode representing the type of error.
errorMessageStringHuman-readable message describing the error.
versionStringTimestamp when the error was returned.

Response Example — Error (4xx)

[
{
"error_code": "some_error_code",
"errorMessage": "Member code is missing",
"version": "2024-10-10T12:21:00.995612Z"
}
]

4.2 Cancel Bet

Endpoint: POST /api/v1/bets/cancel

Note: Cancel bet is only supported by exchanges.

Description: Allows the customer to cancel a bet.

Note: All successful cancel responses will be returned with CANCEL_PENDING status. After the provider has processed the bet results (cancelled or not), they will be sent to the RabbitMQ queue.

Request Headers

Authorization: YOUR_API_TOKEN
Content-Type: application/json

Request Body

FieldTypeDescription
marketIdStringMarket identifier.
betIdStringBet identifier.

Request Body Example

{
"marketId": "9.1223445",
"betId": "2"
}

Response Example — Success (201 Created)

{
"id": 3,
"status": "CANCEL_PENDING",
"version": 1727702890748
}

Response Example — Error (4xx)

[
{
"error_code": "some_error_code",
"errorMessage": "Cancel bet request is not valid",
"version": "2024-10-10T12:21:00.995612Z"
}
]

4.3 Get Outcomes

Endpoint: POST /api/v1/bets/outcomes

Description: Allows the customer to request bet outcomes with optional filtering by market ID and pagination support.

Request Headers

Authorization: YOUR_API_TOKEN
Content-Type: application/json

Request Parameters

FieldTypeRequiredDescription
marketIdStringNoFilter results by specific market ID. If not provided, returns outcomes for all markets.
pageSizeIntegerNoNumber of results per page. Default is 1000 if not specified.
pageNumberIntegerNoPage number starting from 0. Default is 0 if not specified.

Request Body Example

{
"marketId": "14.780243_3",
"pageSize": 20,
"pageNumber": 0
}

Response — Success (200 OK)

FieldTypeDescription
contentArrayArray of bet outcome objects (BetOutcomeSnapshot).
pageNumberIntegerCurrent page number (0-based).
pageSizeIntegerNumber of items per page.
totalElementsLongTotal number of bet outcomes available.
totalPagesIntegerTotal number of pages.
firstBooleanTrue if this is the first page.
lastBooleanTrue if this is the last page.
emptyBooleanTrue if the content array is empty.

Content item fields:

  • requestId (String): Original request ID from the bet.
  • betId (Long): Unique bet identifier.
  • outcome (String): WON, LOST, VOID, etc.
  • pnl (BigDecimal): Customer profit/loss amount.
  • sizeSettled (BigDecimal): Amount that was settled.
  • version (Long): Version timestamp.

Response Example — Success (200 OK)

{
"content": [
{
"requestId": "1e28c0ab-d799-48d0-bb0f-c1c0ef7bfd78",
"betId": 12345,
"outcome": "WON",
"pnl": 150.50,
"sizeSettled": 100.00,
"version": 1704067260995
},
{
"requestId": "2f39d1bc-e8aa-59e1-cc1g-d2d1fg8cgd89",
"betId": 12346,
"outcome": "LOST",
"pnl": -75.25,
"sizeSettled": 75.25,
"version": 1704067261123
}
],
"pageNumber": 0,
"pageSize": 20,
"totalElements": 150,
"totalPages": 8,
"first": true,
"last": false,
"empty": false
}

Notes

  • Authentication Required: Valid Bifrost authorization token with appropriate betting permissions.
  • Pagination: Default page size 1000. Use pageSize and pageNumber to control.
  • Market Filtering: Without marketId, all bet outcomes for the customer are returned.
  • Ordering: Results are ordered by bet ID in descending order (newest first).
  • Customer Scope: Only returns bet outcomes for the authenticated customer.
  • Performance: For better performance, use pagination with smaller page sizes for large result sets.

Error Responses

// 4xx
[{ "error_code": "INVALID_REQUEST", "errorMessage": "Invalid pagination parameters", "version": "2025-01-10T12:21:00.995612Z" }]

// 401 Unauthorized
[{ "error_code": "UNAUTHORIZED", "errorMessage": "Invalid or missing authorization token", "version": "2025-01-10T12:21:00.995612Z" }]

// 403 Forbidden
[{ "error_code": "INSUFFICIENT_PERMISSIONS", "errorMessage": "User does not have permission to access bet outcomes", "version": "2025-01-10T12:21:00.995612Z" }]

4.4 Get Multibet Price

Endpoint: POST /api/v1/bets/multibet/requestprice

Description: Allows the customer to request a price on a multibet.

Note: All successful price requests will have MULTIBET_PRICE_REQUESTED status and all successful price responses will have MULTIBET_PRICE_FILLED status and will be sent into the RabbitMQ queue.

Request Headers

Authorization: YOUR_API_TOKEN
Content-Type: application/json

Request Body

FieldTypeRequiredDescription
requestIdStringNoOptional, random UUID will be generated if not included.
multibetLegsArrayYesFrom 2 to 20 legs. Each leg has: eventId, marketId, side, selectionId.

Request Body Example

{
"requestId": "testRequestId1",
"multibetLegs": [
{
"eventId": "14.221657_1",
"marketId": "14.780243_3",
"side": "BACK",
"selectionId": 2084382
},
{
"eventId": "14.221657_1",
"marketId": "14.780244_3",
"side": "LAY",
"selectionId": 2084370
}
]
}

Response — Success (201 Created)

{
"requestId": "1e28c0ab-d799-48d0-bb0f-c1c0ef7bfd78",
"legs": [
{
"eventId": "14.221657_1",
"marketId": "14.780243_3",
"selectionId": "2084382",
"side": "Back"
},
{
"eventId": "14.221657_1",
"marketId": "14.780244_3",
"selectionId": "2084370",
"side": "Back"
}
]
}

Response — Error (4xx)

[
{
"error_code": "some_error_code",
"errorMessage": "Event id is missing",
"version": "2025-04-10T12:21:00.995612Z"
}
]

4.5 Place Multibet

Endpoint: POST /api/v1/bets/multibet/place

Description: Allows the customer to place a new multibet on an event.

Note: All successful bet responses will be returned with PENDING status. After the provider processes the bet placement, results (placed or not) will be provided in the RabbitMQ queue.

Request Headers

Authorization: YOUR_API_TOKEN
Content-Type: application/json

Request Body

FieldTypeRequiredDescription
stakenumberYesStake amount.
currencyStringYesCurrency code (e.g., "hkd").
memberCodeStringYesCode identifying the member placing the bet.
accountStringYesAccount identifier.
ipStringYesIP address of the client.
stakeFactornumberYesStake factor.
multiBetLegsArrayYesList of legs. Each leg has: marketId, runnerId, side, requestedOdds, expectedOperatorOdds, priceLevelIndex, tolerancePercentage, eventId.
multiBetPricenumberYesCombined multibet price.
positionTakevalue or nullNoPosition take.
fixedPendingUntilvalue or nullNoFixed pending until.
turnoverRebatePercentagevalue or nullNoTurnover rebate percentage.
requestIdStringNoOptional request identifier for idempotency.
placedDatenumberNoTimestamp of placement.

Request Body Example

{
"stake": 10,
"currency": "hkd",
"memberCode": "testCustomer11",
"account": "testCustomer",
"ip": "127.0.0.1",
"stakeFactor": 10,
"multiBetLegs": [
{
"requestedOdds": 2.16,
"expectedOperatorOdds": 2.16,
"priceLevelIndex": 0,
"tolerancePercentage": 0
},
{
"requestedOdds": 9.00,
"expectedOperatorOdds": 9.00,
"priceLevelIndex": 0,
"tolerancePercentage": 0
}
],
"multiBetPrice": 101.626319807,
"positionTake": null,
"fixedPendingUntil": null,
"turnoverRebatePercentage": null,
"requestId": "8c9a42c2-0784-4481-a2b4-97ff528daa08",
"placedDate": 1747396906880
}

Response — Success (201 Created)

Returns the full multibet snapshot including all leg details, multiBetPrice, and a placedTime.

Response — Error (4xx)

[
{
"error_code": "some_error_code",
"errorMessage": "Event id is missing",
"version": "2025-04-10T12:21:00.995612Z"
}
]

5. Key Domain Rules

5.1 oddsType Semantics

MarketCatalogue.oddsType (string) — represents market STRUCTURE:

  • Values seen in practice: LINE, ODDS
  • LINE: Line/session markets (have a target runs/value). Example: Over/Under session markets in cricket.
  • ODDS: Standard odds markets with multiple runners.
  • LINE/ODDS do not determine price encoding. A market may use either HAAR_JEET or DECIMAL.

MarketBook.oddsType (string) — represents PRICING FORMAT (authoritative for display):

  • HAAR_JEET: Price is in paise. e.g. 100 = even money (2.0 decimal), 172 = 2.72 decimal.
  • DECIMAL: Standard decimal odds (e.g. 2.72 means 2.72).
  • Always prefer MarketBook.oddsType over MarketCatalogue.oddsType for determining how to interpret runner prices.

5.2 marketStatus Lifecycle

INACTIVE → OPEN → SUSPENDED → OPEN (can oscillate)
→ CLOSED → SETTLED
BALL_RUNNING (deprecated, treat as suspended/not available)
  • Only OPEN status allows bet placement.
  • BALL_RUNNING is currently supported but planned for deprecation.
  • SUSPENDED and INACTIVE do not allow bets.
  • CLOSED and SETTLED are terminal states.

5.3 tradingMarketType

The tradingMarketType field appears in both MarketCatalogue and MarketBook. It represents the original market type string from Bifrost's internal trading system. It is informational — Hannibal does not need to parse or act on this field for routing or settlement decisions.

5.4 Version Field Usage

The version field on all messages (Category, Event, MarketCatalogue, MarketBook, BetSnapshot, etc.) is a monotonically increasing value used for deduplication and ordering. Upon receiving a message, the consumer should only process and store it if the incoming version is greater than the stored version. Never process a lower version — it is a stale update.

5.5 MarketCatalogue + MarketBook Correlation

  • MarketCatalogue.id == MarketBook.marketId — this is the only join key.
  • MarketCatalogue provides static metadata: market type, name, start time, runner names.
  • MarketBook provides dynamic data: prices, status, limits.
  • A market is valid and bettable only when BOTH MarketCatalogue AND MarketBook are present.

5.6 Side Values

ValueMeaning
BACKSelection occurrence side — bet FOR the selection to happen.
LAYSelection non-occurrence side — bet AGAINST the selection happening. Only supported on exchange markets.

5.7 Cancel Bet Constraint

Cancel bet (POST /api/v1/bets/cancel) is only supported by exchanges. Non-exchange markets (bookmaker/sportsbook type markets within Bifrost) do not support cancellation.


6. Hannibal Integration Notes

Event Model — corrected 2026-07-06

This section supersedes ALL earlier "phantom" / "sub-event" descriptions of Bifrost's event graph. Those terms are dead — they described a model Bifrost never had. Implementation plan: plans/bifrost-event-lifecycle-rebuild.md.

There are no sub-events. Bifrost's Event proto (§3.2) has NO parent field — the only hierarchy in the API is Category.parentId (Country → Competition), never event → event. Every market's MarketCatalogue.eventId points at its real, first-class owning event. A single real-world cricket match may surface as several distinct Bifrost events (e.g. a 22.x match-level event and a 14.x_1 sportsbook event, such as probe 14.302857_1), but each is a full independent event that owns its own markets — none is a "child" or "sub-event" of another. (Observed event-id formats are 22.x and 14.x_1; note 9.x is a market-id prefix — fancy/session markets, see the Market ID format section — never an event id.) IDs decide identity; names are display-only. There is zero name-derived linkage.

Orphaned event markets (the real thing the old "phantom" term was groping at): a cluster of markets whose owning Event object we evicted or never consumed, while the market catalogues/books themselves survived in cache. The event is absent; its markets are stranded. This is a cache-completeness gap on our side, not a Bifrost data structure. (For contrast, Betfair's listMarketCatalogue simply stops returning CLOSED markets — for Bifrost that erasure is our job.)

Recovery resolves ANY event by explicit id. POST /api/v1/recovery/events {"eventIds":[…]} returns a full Event for any id — including events years old. Verified live 2026-07-06:

eventIdnoteBetfair externalId
22.148238Durham v Lancashire, Jun-935682714
22.148204resolved35646822
22.105492from 2024-03-07 — 2+ years old, still resolves33071642
14.302857_1resolved with status = CANCELLED

So an orphaned owner is never a dead end: ask Bifrost by id (resolve), never guess or name-match. Fail closed while the owner is unresolved.

The servability invariant (the single rule the whole lifecycle serves):

servable(market) ⇔ owner event (catalogue.eventId) is cached AND non-terminal
  • Match completes ⇒ the event and ALL its market data leave the system. Orders/settlement live in the DB keyed by betId and are untouched (the bet consumer reads no market cache).
  • Unknown owner ⇒ resolve by id; never adopt the markets onto a different event.

Incident this corrects (Jun-2026): markets whose 22.x owner had been evicted were name-matched onto a different return-leg fixture of the same team pair → bets rejected 2029 Invalid market ID at Bifrost + false LIVE badges on the carousel. No money lost (decline + refund worked), but the name-matching was the bug. The fix is id-only ownership + resolve-or-evict.

OddsType Semantics (Confirmed with Michael Cheremuhin, 2026-03-09)

  • MarketCatalogue.oddsType: LINE or ODDS — represents market STRUCTURE (trading system internals).
    • LINE: line/session markets (have a target runs value).
    • ODDS: standard odds markets (multiple runners).
    • LINE/ODDS do not select price format. 14.x has been observed with both HAAR_JEET and DECIMAL; market prefix is not price evidence.
  • MarketBook.oddsType: HAAR_JEET or DECIMAL — represents PRICING FORMAT (authoritative for display).
    • MarketBook.oddsType is authoritative for display and financial conversion and is frozen at placement in the durable intent.
    • If MarketBook.oddsType = HAAR_JEET: price is in paise (e.g. 100 = even money, 172 = 2.72 decimal).
    • If MarketBook.oddsType = DECIMAL: standard decimal odds.

Queue Usage

  • bets.snapshot.queue: ALL bet status updates (acceptance, rejection, cancellation, voiding, size matching).
  • bets.outcomes.queue: Settlement information only.
  • bets.cancel.queue: Cancelled bet snapshots (also BetSnapshot, but separate queue for cancellation flow).
  • NO separate "bets.cancel.queue" carries a different message type — it still produces BetSnapshot.

PlaceBet: providerId field

NOT in current API spec. Was removed. Do not send.

MarketCatalogue + MarketBook Correlation

  • MC.id == MB.marketId (only join key).
  • MC: static metadata (market type, name, start time, runner names).
  • MB: dynamic data (prices, status, limits).
  • A market is valid/bettable only when BOTH MC and MB are present.
  • Michael: "we recommend to store both entities and consider market to be correct and available for betting if both parts are present."

LAY Bets on 9.x Markets

  • Bifrost API supports side: LAY syntactically.
  • LAY is defined as: selection non-occurrence side selection.
  • Cancel bet is exchange-only (9.x markets are exchange markets in Bifrost's classification).
  • All LAY bets currently fail with error code 0.1001 — under investigation with Bifrost.

Classification versus Forsyt execution contract: the provider's 9.x classification above does not override the lifecycle actually integrated by Forsyt. Current 9.x and 14.x placement is all-or-nothing: PLACED plus validated size is authoritative. Missing sizeMatched is non-blocking; when present it must equal size, and any other exchange-only bucket that is present must be zero. Hannibal rejects contradictory bucket evidence rather than silently changing exposure accounting. Future 1.x exchange snapshots use the absolute exchange buckets and weighted averageOdds, but real-money placement stays blocked until that execution model is enabled end to end. See ADR-0016.

Odds Conversion (Hannibal Internal)

  • Hannibal's canonical display odds are decimal, while the placement intent also preserves the exact provider-native odds and price format observed at quote time.
  • BifrostAdapter uses haarJeetToDecimal() for canonical read/display. Placement sends the exact preserved native odds; it must not reconstruct provider odds from decimal display odds.
  • Haar jeet formula:
    • decimal = 1 + (haarJeet / 100)
    • Mathematical inverse: haarJeet = (decimal - 1) * 100 (reference only, not placement authority)
  • Example: haarJeet = 100decimal = 2.0 (even money); haarJeet = 172decimal = 2.72.

Currency

  • The Bifrost request sends config.bifrost.currency, sourced from BIFROST_CURRENCY and currently defaulted to HKD. This is the provider API's currency label.
  • Observed MarketBook payloads use lower-case currency-map keys such as maxStakeCurrencies.hkd. Hannibal compares those keys to the configured provider API/map currency case-insensitively, rejects duplicate case variants or invalid amounts, and preserves zero.
  • For 9.x/14.x, only the currency-qualified maxStakeCurrencies value authorizes a displayed price-level limit. Hannibal does not infer it from scalar maxStake or exchange-liquidity size.
  • Placement amount conversion separately uses Provider.currency through the global CurrencyRate model. Provider-specific synthetic accounting codes are valid there, so this may differ textually from the BIFROST_CURRENCY request/map label.
  • Direct WebSocket depth deltas label their conversion basis with Provider.currency, matching the HTTP limits path and the authenticated sizeRates map.
  • There is no FP denomination or Provider.exchangeRate field. The currency field is optional in the provider API schema, but Hannibal's current request always supplies the configured value.

memberCode

  • MUST be order.userId (the Hannibal user's UUID). NOT a static platform config value.
  • Using a static memberCode breaks per-user risk management on Bifrost's side.

Bet Status Mapping (current 9.x / 14.x contract)

Bifrost BetSnapshot.statusHannibal interpretation
PENDINGAcknowledgement only; the order remains financially unmatched.
PLACEDAfter identity and size validation, atomically apply the full match.
PARTIALLY_MATCHED / UNMATCHEDContradicts the all-or-nothing fill policy; no mutation, DLQ, and alert.
FAILEDBefore PLACED, decline and release the unmatched contribution; never erase an applied match.
CANCELLED / LAPSEDBefore PLACED, dispose the full unmatched contribution; never erase an applied match.
VOIDEDSettlement-axis invalidation of a confirmed match, handled by the void path.

Future 1.x exchange execution is partial-fill capable but remains blocked for real-money placement until its routing, position-taking, cancellation, and post-placement behavior are enabled and verified end to end. See ADR-0016.

Settlement Mapping (Bifrost → Hannibal)

Bifrost BetOutcomeSnapshot.outcomeHannibal settlementOutcome
WONwin
LOSTlose
VOIDvoid