Manage bonus bet tokens, promotional offers, and boost multipliers. View available tokens, check split options, split large tokens, and query promos for specific races.
Base URL: https://api.b337.ai
All four endpoints below -
/v3/promos,/v3/bonus_bets,/v3/promo_tokens,/v3/split_bonus_bet- use the same async contract as/v3/place_bet: a 202 response in ~100ms with acorrelation_id, then poll/api/bet_status(or receive a signed webhook atcallback_url) for the actual result.The
resultfield on the poll response carries the same payload shape the legacy v2 endpoints used to return inline - so existing parsing code only needs to know where to read it from.Placing bets with promo tokens: the bonus / saver / boost flags shown below (
use_bonus_bet,use_boost,promo_name,bet_type) all work on/v3/place_bet- same payload, async response.
⚠️ V2 deprecation - sunset 2026-05-17. The endpoints
/v2/promos,/v2/bonus_bets,/v2/promo_tokens, and/v2/split_bonus_betare deprecated. They still function but now return:Deprecation: true Sunset: Sun, 17 May 2026 00:00:00 GMT Link: </v3/<successor>>; rel="successor-version"After 17 May 2026 they will return HTTP 410. Migrate to the v3 equivalents below.
/v2/session_promo_summaryand/v2/boost_tokenare not affected.
Every v3 endpoint on this page returns the same 202 envelope as
/v3/place_bet:
{ "status": "pending", "correlation_id": "9b3c…uuid…", "session_id": "uuid", "bookie": "tab", "username": "user@example.com", "submitted_at": "2026-05-14T10:00:00.000Z", "timeout_at": "2026-05-14T10:05:00.000Z", "callback_url": null }
Retrieving the result and webhook semantics (signature verification, retry
behaviour, status values) are identical across all v3 endpoints - see the
Async API (v3) guide. The polling endpoint
POST /api/bet_status works for any correlation_id - place_bet,
withdraw-balance, price_check, and the four endpoints below.
POST /v3/bonus_bets {#bonus-bets}Retrieve all active bonus bet tokens with their split options. Read-only.
| Field | Type | Required | Description |
|---|---|---|---|
session_id | string | Yes | Active session ID |
callback_url | string | No | If set, the result is POSTed to this URL when ready. |
{ "session_id": "uuid" }
Returns the standard async envelope (see above). Poll /api/bet_status
with the correlation_id for the result.
result payload{ "success": true, "bonus_bets": [ { "id": "token_12345", "amount": 50.00, "status": "AVAILABLE", "split_options": [2, 5, 10], "split_amounts": [25.00, 10.00, 5.00], "expiry": "2026-02-15T00:00:00Z", "description": "Bonus Bet" } ], "total_bonus": 50.00, "count": 1 }
| Field | Type | Description |
|---|---|---|
| id | string | Unique token identifier |
| amount | float | Token value in dollars |
| status | string | Token status (AVAILABLE, USED, etc.) |
| split_options | array | Number of pieces the token can be split into |
| split_amounts | array | Value per piece for each split option |
| expiry | string | Expiration date (ISO 8601) |
| description | string | Token description |
Deprecated:
POST /v2/bonus_bets- same request body, returns the same payload inline synchronously. Sunset 2026-05-17. Migrate to v3.
POST /v3/split_bonus_bet {#split-bonus-bet}Split a large bonus bet token into smaller pieces. State-mutating - fires on the bookie as soon as the 202 returns.
| Field | Type | Required | Description |
|---|---|---|---|
session_id | string | Yes | Active session ID |
max_amount | float | Yes | Maximum amount per piece after split (positive) |
callback_url | string | No | If set, the result is POSTed to this URL when ready. |
{ "session_id": "uuid", "max_amount": 5.00 }
The server automatically:
max_amount)Standard async envelope.
⚠️ Do not retry the submission after a 202. Once the 202 comes back, the split has been queued for the bookie. If the dashboard or your client loses the response (network blip, tab close), retry the request and you risk splitting the user's token twice.
The safe pattern is: persist the
correlation_idreturned in the 202 immediately, and on any failure surface that ID to the user so they can poll/api/bet_statusfor the result instead of re-firing. Re-submitting is only safe if the request failed before the 202 came back (network error, 4xx/5xx from the dashboard).
result payload{ "success": true, "message": "Split $50 token into 10 x $5.00 pieces", "original_amount": 50.00, "new_amount_per_bet": 5.00, "num_splits": 10, "token_id": "token_12345" }
{ "success": false, "message": "No splittable bonus bets found" }
Deprecated:
POST /v2/split_bonus_bet- same request, returns the same payload inline. Sunset 2026-05-17. Note the v2 response usednew_amount/num_piecesinstead of the v3new_amount_per_bet/num_splits- clients still consuming v2 should rename when migrating.
POST /v3/promos {#promos}Fetch personalized promotional offers available for a session. Read-only.
| Field | Type | Required | Description |
|---|---|---|---|
session_id | string | Yes | Active session ID |
callback_url | string | No | If set, the result is POSTed to this URL when ready. |
{ "session_id": "uuid" }
Standard async envelope.
result payload{ "success": true, "promos": [ { "track": "Rosehill", "race_num": 6, "race_type": "R", "promo_type": "2/3", "max_reward": 50.0, "token_group_id": "D290126036832-264018", "valid_till": "2026-01-31T04:30:00.000Z", "event_url": "/racing/2026-01-31/ROSEHILL/RSH/R/6/Win", "offer_name": "Rosehill Race Saver", "offer_type": "stake_back" } ], "count": 15 }
| Field | Type | Description |
|---|---|---|
| track | string | Track the promo applies to |
| race_num | int | Race number |
| race_type | string | Race type code (R, G, H) |
| promo_type | string | Promo type (e.g., "2/3" for 2nd/3rd cashback) |
| max_reward | float | Maximum cashback/reward amount |
| token_group_id | string | Token group identifier |
| valid_till | string | Expiration timestamp (ISO 8601) |
| offer_name | string | Display name of the offer |
| offer_type | string | Offer type (e.g., "stake_back") |
Deprecated:
POST /v2/promos- same request, returns the same payload inline. Sunset 2026-05-17.
POST /v3/promo_tokens {#promo-tokens}Get all promo tokens available for a specific race. Returns Race Saver tokens and Bonus Boost tokens. Saver tokens are sorted with race-specific ones first. Read-only.
| Field | Type | Required | Description |
|---|---|---|---|
session_id | string | Yes | Active session ID |
track | string | Yes | Track name |
race_num | int | Yes | Race number (>0) |
race_type | string | No | "(R)", "(G)", "(H)". Default: "(R)" |
country | string | No | Country filter |
callback_url | string | No | If set, the result is POSTed to this URL when ready. |
{ "session_id": "uuid", "track": "Eagle Farm", "race_num": 6, "race_type": "(R)" }
Standard async envelope.
result payload{ "success": true, "saver_tokens": [ { "type": "2/3", "max_reward": 50.0, "is_race_specific": true, "usage_restrictions": "Eagle Farm R6" } ], "bonus_boost": { "available": true, "boost_percentage": 25, "max_reward": 100.0 } }
Deprecated:
POST /v2/promo_tokens- same request, returns the same payload inline. Sunset 2026-05-17.
Use bonus bet tokens when placing bets by adding use_bonus_bet: true to
your bet request. Recommended endpoint:
POST /v3/place_bet.
{ "session_id": "uuid", "category": "racing", "stake": 5.00, "market": "win", "track": "Flemington", "race_num": 3, "race_type": "(R)", "date": "2026-02-03", "runner": "Horse Name", "use_bonus_bet": true }
| Field | Type | Required | Description |
|---|---|---|---|
| use_bonus_bet | bool | No | If true, uses bonus bet balance instead of cash |
The system automatically selects a bonus bet token matching the stake. If no suitable token exists, the bet fails.
result payload (from /api/bet_status){ "success": true, "bet_id": "xxx", "odds": 3.50, "stake": 5.00, "is_bonus_bet": true, "bonus_token_id": "token_12345", "market": "win", "runner": "Horse Name" }
When placing bets, specify a bet_type to use specific promo tokens.
Works identically on /v3/place_bet.
{ "session_id": "uuid", "category": "racing", "stake": 10.00, "market": "win", "track": "Flemington", "race_num": 3, "runner": "Horse Name", "bet_type": "2/3" }
| bet_type | Description |
|---|---|
"2" | 2nd place cashback (stake back if runner finishes 2nd) |
"2/3" | 2nd or 3rd place cashback |
"2/3/4" | 2nd, 3rd or 4th place cashback |
{ "session_id": "uuid", "category": "racing", "stake": 10.00, "market": "win", "track": "Flemington", "race_num": 3, "runner": "Horse Name", "bet_type": "bonus_betboost_25pc" }
| bet_type | Description |
|---|---|
"bonus_betboost_25pc" | 25% of winnings paid as bonus bet |
"bonus_betboost_50pc" | 50% of winnings paid as bonus bet |
{ "session_id": "uuid", "category": "racing", "stake": 10.00, "market": "win", "track": "Flemington", "race_num": 3, "runner": "Horse Name", "bet_type": "pump" }
| bet_type | Description |
|---|---|
"pump" | Your odds are boosted and any uplift is paid in cash, not a bonus bet. Subject to the token's own stake cap. |
POST /v2/boost_tokenCheck whether the boost token (multiplier) is available for a given race. The boost token lets you multiply winnings (2x, 3x) by paying more stake - distinct from promo tokens.
Still on the v2 sync surface - not affected by the 2026-05-17 sunset that hits the four migrated endpoints above. Use as-is.
{ "session_id": "uuid", "track": "Eagle Farm", "race_num": 6, "race_type": "(R)" }
| Field | Type | Required | Description |
|---|---|---|---|
| session_id | string | Yes | Active session ID |
| track | string | Yes | Track name |
| race_num | int | Yes | Race number |
| race_type | string | No | "(R)", "(G)", "(H)". Default: "(R)" |
{ "success": true, "boost_token_available": true }
import requests, time API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.b337.ai" HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"} def await_result(cid: str, timeout_at: str, poll_interval: float = 2.0): """Generic v3 poll - same shape for any correlation_id on this site.""" deadline = time.mktime(time.strptime(timeout_at[:19], "%Y-%m-%dT%H:%M:%S")) while time.time() < deadline: r = requests.post( f"{BASE_URL}/api/bet_status", headers=HEADERS, json={"correlation_ids": [cid]}, timeout=10, ).json() entry = r["statuses"][0] if entry["status"] != "pending": return entry # status: completed | timeout | unknown time.sleep(poll_interval) return None # 1. Check available bonus bets submit = requests.post( f"{BASE_URL}/v3/bonus_bets", headers=HEADERS, json={"session_id": "uuid"}, ).json() bonus_entry = await_result(submit["correlation_id"], submit["timeout_at"]) bonus_payload = bonus_entry["result"] if bonus_entry else None # 2. Split a large token if needed. State-mutating - never auto-retry # after the 202; if the poll fails, surface the correlation_id to the # user and let them recheck rather than re-submitting. split_submit = requests.post( f"{BASE_URL}/v3/split_bonus_bet", headers=HEADERS, json={"session_id": "uuid", "max_amount": 5.00}, ) split_submit.raise_for_status() split_body = split_submit.json() split_cid = split_body["correlation_id"] # PERSIST IMMEDIATELY print(f"Split submitted, correlation_id={split_cid}") split_entry = await_result(split_cid, split_body["timeout_at"]) if split_entry is None: raise RuntimeError( f"Split timed out. DO NOT re-submit. Recheck with correlation_id={split_cid}" ) print(split_entry["result"]) # 3. Place bet with bonus balance (v3, same correlation_id flow) bet_submit = requests.post( f"{BASE_URL}/v3/place_bet", headers=HEADERS, json={ "session_id": "uuid", "category": "racing", "stake": 5.00, "market": "win", "track": "Flemington", "race_num": 3, "race_type": "(R)", "runner": "Horse Name", "use_bonus_bet": True, }, ).json() bet_entry = await_result(bet_submit["correlation_id"], bet_submit["timeout_at"]) print(bet_entry["result"] if bet_entry else "bet timed out")