Build bots and integrations on top of SideBet.vip. Create manual or automatically settled sports bets, join offers, read your history and receive signed webhooks. Machine-readable version for LLMs and scripts: /docs/api.md.
SideBet.vip runs two fully separated environments. FUN uses demo credits (1000 on signup) and is the default. REAL uses a real-money balance that starts at zero and requires a one-off acceptance of the real-money terms in the web app. Balances, offers, bets, history, rankings and referral commissions never cross between them, and a FUN offer can only be matched by a FUN bet.
An API key is bound to one environment when you create it and always acts there. OAuth tokens default to FUN; send X-SideBet-Mode: REAL to act in real money. Every bet, transaction and account response carries a mode field, and listings only return objects from the caller's environment.
Generate a key in your account page. The secret is shown once — store it safely. Send it as a bearer token on every request. Keys are either read (GET only) or full (create, join, cancel, confirm). Rate limit: 60 requests per minute per key.
curl https://sidebet.vip/api/public/v1/me \ -H "Authorization: Bearer sbv_live_..."
Building an app that acts on behalf of other users? Use OAuth 2.0 with PKCE instead of asking users for their API keys. Register your client through the dynamic client registration endpoint, send users through the SideBet consent screen, and call the API with the resulting access token as a bearer token — OAuth tokens have full API scope.
/.well-known/oauth-authorization-server/oauth/register/.well-known/oauth-protected-resourcecurl https://sidebet.vip/api/public/v1/me \ -H "Authorization: Bearer <oauth_access_token>"
GET/api/public/v1/mescope: read
GET/api/public/v1/categoriesscope: read
GET/api/public/v1/fixtures?league=Ekstraklasascope: read
GET/api/public/v1/weather-markets?city=Warsawscope: read
GET/api/public/v1/bets?status=OPEN&mine=false&limit=50scope: read
GET/api/public/v1/bets/{betId}scope: read
POST/api/public/v1/betsscope: full
POST/api/public/v1/bets/matchscope: full
POST/api/public/v1/bets/weatherscope: full
POST/api/public/v1/bets/{betId}/joinscope: full
POST/api/public/v1/bets/{betId}/cancelscope: full
POST/api/public/v1/bets/{betId}/confirmscope: full
GET/api/public/v1/transactions?limit=100scope: read
All responses are JSON with camelCase keys; collections come wrapped in a named array. Always pass the id you received back as marketId / fixtureId — never rebuild it from the station code.
GET /api/public/v1/weather-markets?city=Warsaw
{
"markets": [
{
"id": "wx:warsaw:2026-09-10", // pass this as marketId
"city": "Warsaw",
"country": "Poland",
"timezone": "Europe/Warsaw",
"marketDate": "2026-09-10", // local calendar day covered
"thresholdC": 19.5, // the line, always in Celsius
"forecastTmax": 21.3, // forecast only, never settles
"closesAt": "2026-09-09T22:00:00.000Z", // local midnight, betting stops
"dayEndAt": "2026-09-10T22:00:00.000Z", // next local midnight
"resolvesAt": "2026-09-10T22:10:00.000Z", // settlement, +10 minutes
"stationIcao": "EPWA",
"stationName": "Warsaw Chopin",
"unit": "C", // "C" or "F" - the station's unit
"bandLabel": "19°C", // outcome band shown to users
"lineLabel": "19–20°C", // whole degrees around the line
"aboveLabel": "20°C and above", // wins for pick "ABOVE"
"belowLabel": "19°C or lower" // wins for pick "BELOW"
}
]
}GET /api/public/v1/fixtures?league=Ekstraklasa
{
"fixtures": [
{
"id": "2052881", // pass this as fixtureId
"league": "Ekstraklasa",
"homeTeam": "Legia Warszawa",
"awayTeam": "Lech Poznań",
"homeBadge": "https://.../legia.png",
"awayBadge": null,
"kickoffAt": "2026-09-12T17:30:00.000Z",
"venue": "Stadion Wojska Polskiego"
}
]
}POST /bets, /bets/match, /bets/weather -> 201
{ "betId": "08c5dfef-7d71-4a0a-9fad-001597e4bb2e" }
POST /bets/{betId}/join | cancel | confirm -> 200
{ "ok": true }curl -X POST https://sidebet.vip/api/public/v1/bets \
-H "Authorization: Bearer sbv_live_..." \
-H "Content-Type: application/json" \
-d '{
"title": "BTC above 100k on Friday",
"description": "Bitcoin closes above 100,000 USD on Friday 23:59 UTC.",
"categoryId": "<category uuid>",
"creatorSide": "YES",
"stakeAmount": 100,
"startsAt": "2026-09-10T10:00:00Z",
"endsAt": "2026-09-12T23:59:00Z",
"resolutionSource": "Binance BTC/USDT daily close"
}'curl -X POST https://sidebet.vip/api/public/v1/bets/match \
-H "Authorization: Bearer sbv_live_..." \
-H "Content-Type: application/json" \
-d '{ "fixtureId": "2052881", "pick": "HOME", "stakeAmount": 50, "safeClose": true, "partialFill": true }'curl -X POST https://sidebet.vip/api/public/v1/bets/weather \
-H "Authorization: Bearer sbv_live_..." \
-H "Content-Type: application/json" \
-d '{ "marketId": "wx:warsaw:2026-09-10", "pick": "ABOVE", "stakeAmount": 50, "safeClose": true, "partialFill": true }'Optional safeClose cancels an unmatched bet automatically and refunds the stake — at kickoff for match bets, at 12:00 local time in the city for weather bets — so nobody can join once the outcome is already leaning one way. Optional partialFill (creator-only, auto-settled bets) pairs an unmatched bet with the biggest opposing offer on the same event at close time: the smaller bet closes as MERGED and its creator joins the bigger bet at the smaller stake, with any surplus refunded. Match bets always settle from the official result. A draw voids the bet and both stakes are returned in full. Weather bets settle 10 minutes after local midnight from the METAR station's daily maximum temperature; the line always falls between two whole station degrees, so a tie is impossible.
Set one https callback URL in your account. We POST JSON for bet.matched, bet.settled, bet.refunded and bet.disputed. Every request carries x-sidebet-event and x-sidebet-signature, an HMAC-SHA256 hex digest of the raw body using your signing secret.
const expected = crypto
.createHmac("sha256", process.env.SIDEBET_WEBHOOK_SECRET)
.update(rawBody)
.digest("hex");
if (expected !== req.headers["x-sidebet-signature"]) return res.status(401).end();Errors return a JSON body { "error": "..." } with status 400 (invalid request), 401 (bad key), 403 (read-only key), 404 (not found) or 429 (rate limit). Withdrawals are not available through the API.