JSON over HTTP. Amounts are whole sats. Odds are decimal and must sit on the price ladder from /api/config. Stakes are the backer's stake on both sides.
Get a challenge, sign it as a Nostr event (kind 27235) with a challenge tag, and post it. Send the token as Authorization: Bearer <token>. Tokens last 30 days.
GET /api/challenge → { challenge }
POST /api/login { event } → { token, pub }
POST /api/logout
/api/configChain, ladder, minimum stake, bet delay, commission, withdrawal fee.
/api/marketsEvery fixture with its Match Odds book (best price each side), live score, status and its other markets.
/api/markets/:idOne market: a three-deep book per selection (crossed levels marked virtual), recent trades, price history, near or actual SP, and, when logged in, your position and a cash-out quote. Match Odds has the fixture id; the others are <fixture>.ht, .ou15, .ou25, .ou35, .btts, .cs.
/api/streamServer-sent events: a version number each time anything changes.
/api/ordersPlace an order. 30 order actions per 10 s per user.
{ "market": "gw9-liv-ars", "runner": "H", "side": "back", "odds": 2.5, "stake": 1000,
"persist": "lapse" | "keep" | "sp", // what the unmatched part does at kick-off
"tif": "fok" } // optional: match now or cancel the rest
{ "market": "gw9-liv-ars", "runner": "H", "side": "lay", "sp": true, "liability": 1000, "limit": 3 }
// a starting price bet: back takes "stake", lay takes "liability"
Returns { order, bets }. In play, the order comes back delayed and reaches the book after the bet delay. SP orders come back as sp and match at kick-off.
/api/orders/:id { odds?, stake? }A smaller stake keeps the order's place in the queue; a new price cancels it and places a fresh one.
/api/orders/:idCancel one order.
/api/orders?market=:idCancel all your unmatched orders, in one market or everywhere.
/api/cashout { market }Hedge the market at the best prices so every outcome pays the same.
/api/meBalance, exposure, available, positions, orders, bets, P&L by market, statement, withdrawals, deposit address.
/api/me/statement.csvThe statement as CSV.
/api/withdraw { amount, to?, event }event is a kind 27235 event signed by your key with tags challenge, withdraw (the amount) and to (the address). to is accepted on txbt4 only.
/api/faucetSidechain only: ask the faucet for test sats to your own address.
const B = 'https://melvin.me/public/oddstr', h = { 'content-type': 'application/json', authorization: `Bearer ${token}` };
const m = await (await fetch(`${B}/api/markets/gw9-liv-ars`)).json();
const best = m.runners[0].back[0]; // best price to back the home win
await fetch(`${B}/api/orders`, { method: 'POST', headers: h,
body: JSON.stringify({ market: m.id, runner: 'H', side: 'back', odds: best.odds, stake: 500, tif: 'fok' }) });
bots/marketmaker.mjs in the repo is a full market maker built on this API.