Read and withdraw funds from a bookmaker account via an active session. This page covers two endpoints:
POST /v3/balance. Synchronous -
returns the balance directly in the response body.POST /v3/withdraw_balance.
Async - returns 202 + a correlation_id, same contract as the
other v3 endpoints.Base URL: https://api.b337.ai
Both endpoints require API key authentication:
X-API-Key: YOUR_API_KEYContent-Type: application/jsonPOST /v3/balance
Unlike the other v3 endpoints (/v3/place_bet, /v3/withdraw_balance,
/v3/price_check), Get Balance is synchronous - it does not return
202 + a correlation_id. The server reads the balance straight from the
Redis cache and returns it inline, typically in ~tens of milliseconds.
The balance cache is kept warm - it is eagerly seeded from Supabase when a
session starts and refreshed on every session heartbeat. Treat the response
as the final answer: do not poll /api/bet_status for this endpoint.
| Field | Type | Required | Description |
|---|---|---|---|
session_id | string (UUID) | Yes | Active session ID to read the balance for |
{ "session_id": "uuid" }
{ "session_id": "uuid", "bookie": "tab", "username": "user@example.com", "balance": 142.50, "bonus_balance": 25.00, "withdrawable_balance": 117.50, "retrieved_at": "2026-05-15T10:00:00.000Z" }
| Field | Type | Description |
|---|---|---|
balance | number | Total account balance (cash + bonus) |
bonus_balance | number | Portion of the balance held as bonus / promo funds |
withdrawable_balance | number | Cash available to withdraw right now |
retrieved_at | string | When the cached balance was last refreshed (ISO 8601) |
curl -X POST https://api.b337.ai/v3/balance \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"session_id": "your-session-uuid"}'
import requests API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.b337.ai" HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"} resp = requests.post( f"{BASE_URL}/v3/balance", headers=HEADERS, json={"session_id": "your-session-uuid"}, timeout=15, ) resp.raise_for_status() balance = resp.json() print(f"Withdrawable: ${balance['withdrawable_balance']:.2f}")
400 - session_id missing or invalid401 - Unauthorized404 - Session not found or not owned by you500 - Internal server errorPOST /v3/withdraw_balance
Initiate a withdrawal from a bookmaker account via an active session. The
endpoint is async - it returns in ~100ms with a correlation_id and
the actual bookie withdrawal runs in the background. Retrieve the final
result by polling /api/bet_status or by registering a webhook
callback_url.
This uses the same async contract as POST /v3/place_bet. If you've
already wired up polling/webhooks for place_bet, the withdraw flow is a
drop-in: same status states, same callback shape, same polling endpoint.
| Field | Type | Required | Description |
|---|---|---|---|
session_id | string (UUID) | Yes | Active session ID to withdraw from |
amount | number | Yes | Positive decimal amount to withdraw |
callback_url | string | No | If set, the result is POSTed to this URL when ready (http:// or https://). If absent, poll for the result. |
{ "session_id": "uuid", "amount": 50.00, "callback_url": "https://your-app.example.com/hooks/withdraw" }
{ "status": "pending", "correlation_id": "9b3c…uuid…", "session_id": "uuid", "bookie": "tab", "username": "user@example.com", "amount": 50.00, "submitted_at": "2026-05-14T10:00:00.000Z", "timeout_at": "2026-05-14T10:05:00.000Z", "callback_url": "https://your-app.example.com/hooks/withdraw" }
status is always literal "pending" on submission. timeout_at is
~5 minutes from submitted_at - cap your polling at that time.
If callback_url registration fails, the field comes back as null -
fall back to polling in that case.
POST /api/bet_status with the correlation_id until status flips off
pending.
curl -X POST https://api.b337.ai/api/bet_status \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"correlation_ids": ["9b3c…uuid…"]}'
Response:
{ "statuses": [ { "correlation_id": "9b3c…uuid…", "status": "completed", "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", "result": { "success": true, "message": "Withdrawal initiated", "withdrawal_id": "TAB-W-12345", "status": "pending", "bookie": "tab" } } ] }
When status === "completed", the result field is the bookie payload -
the same shape the legacy sync endpoint used to return inline. Poll every
~2-3 seconds; stop when status is not pending.
status values:
pending - withdrawal is in flight, keep polling.completed - withdrawal finished, read result.timeout - server gave up waiting for the client. Treat as a transient failure.unknown - correlation_id expired (over 30 minutes since submission).The polling endpoint is named
/api/bet_statusfor historical reasons - it works for any correlation_id (place_bet, withdraw_balance, price_check).
Provide callback_url in the original request and the result will be POSTed
to your URL when ready.
Requirements: your URL must be reachable from api.b337.ai and
respond 2xx within 10 seconds.
Inbound POST shape:
POST {your callback_url}
Headers:
Content-Type: application/json
Bet-Correlation-Id: {correlation_id}
Bet-Signature: sha256=<HMAC-SHA256(api_key, body)>
{ "correlation_id": "9b3c…uuid…", "result": { "success": true, "message": "Withdrawal initiated", "withdrawal_id": "TAB-W-12345", "status": "pending", "bookie": "tab" }, "metadata": { "bookie": "tab", "username": "user@example.com", "session_id": "uuid" }, "delivered_at": "2026-05-14T10:00:02.500Z" }
Signature verification and retry semantics (3 retries with exponential backoff at 1s/4s/16s) are identical to place_bet. See the Async API (v3) guide for the verification snippet.
import requests import time API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.b337.ai" HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"} def withdraw_balance(session_id: str, amount: float, poll_budget: float = 300.0) -> dict: """Submit a withdrawal and poll until it completes. Returns the bookie result payload (success, message, withdrawal_id, ...). """ submit = requests.post( f"{BASE_URL}/v3/withdraw_balance", headers=HEADERS, json={"session_id": session_id, "amount": amount}, timeout=15, ) submit.raise_for_status() body = submit.json() cid = body["correlation_id"] timeout_at = body["timeout_at"] # iso8601 deadline = min(time.time() + poll_budget, time.mktime(time.strptime(timeout_at[:19], "%Y-%m-%dT%H:%M:%S"))) while time.time() < deadline: status = requests.post( f"{BASE_URL}/api/bet_status", headers=HEADERS, json={"correlation_ids": [cid]}, timeout=10, ) status.raise_for_status() entry = status.json()["statuses"][0] if entry["status"] == "completed": return entry["result"] if entry["status"] in ("timeout", "unknown"): return {"success": False, "error": entry["status"], "transient": True} time.sleep(2) return {"success": False, "error": "Poll budget exhausted", "transient": True} result = withdraw_balance("your-session-uuid", 50.00) if result["success"]: print(f"Withdrawn: {result.get('withdrawal_id')}") else: print(f"Failed: {result.get('error') or result.get('message')}")
The bookie result inside statuses[0].result (or the webhook's result
field) has the following shape:
| Field | Type | Description |
|---|---|---|
success | bool | Whether the withdrawal was accepted by the bookie |
message | string | Human-readable status / error message |
withdrawal_id | string | Bookie-side reference for the withdrawal (when available) |
status | string | pending | completed | failed - the bookie's own state for the withdrawal (separate from the async job state) |
bookie | string | Bookie identifier |
400 - session_id or amount missing/invalid401 - Unauthorized404 - Session not found or not owned by you500 - Internal server error