ライブオッズAPI:WebSocketでリアルタイム配信
ライブオッズAPIは、ブックメーカーの価格が変わった瞬間にその値をコードへ届けます。Odds-API.ioは、34競技・450以上のブックメーカーのプレマッチおよびインプレーオッズを1本のWebSocket接続で配信し、ライブスコア、試合ステータス、試合時計も同じストリームで提供します。
WebSocketストリーミングは、すべての有料プランに追加できるアドオンです。RESTはすべてのプランに含まれます。
- ブックメーカー
- 450+
- 競技
- 34
- レイテンシ
- <150ms
- リプレイ期間
- 24h
ポーリングではなくWebSocketを使う理由
ポーリングは一定間隔でオッズを問い合わせるため、返ってくるデータの大半は変化していません。WebSocketは接続を開いたまま、動いた価格だけを運びます。そのため変化をより早く受け取れ、転送量も大幅に減ります。
| 項目 | RESTポーリング | WebSocket |
|---|---|---|
| 変化の受け取り方 | 一定間隔で再度リクエスト | 価格が変わった時点でプッシュ |
| レイテンシ | ポーリング間隔+リクエスト時間 | <150ms |
| 接続 | ポーリングごとに新しいHTTPリクエスト | 開いたままの1本の接続 |
| 取り下げられたマーケット | イベントを再取得しないと気づけない | deletedとno_marketsのメッセージで明示 |
| 適した用途 | スクリプト、バックフィル、簡単な照会 | ライブベッティングアプリ、インプレーモデル、アービトラージスキャナー |
数行で接続
APIキーと必要なマーケットを指定してwss://api.odds-api.io/v3/wsを開きます。最初のメッセージはフィルターを確認するウェルカムメッセージで、その後はオッズが変化するたびに届きます。
// npm i ws
import WebSocket from 'ws'
const API = 'https://api.odds-api.io/v3'
const apiKey = process.env.ODDS_API_KEY
const markets = 'ML,Spread,Totals'
const eventIds = '63017989,63017990' // up to 10, for example from GET /v3/events/live
const bookmakers = 'Bet365,Unibet' // the bookmakers selected on your account
const board = new Map()
let lastSeq = 0
let needsRebuild = false
function connect() {
const params = new URLSearchParams({ apiKey, markets, eventIds, channels: 'odds,scores,status' })
if (lastSeq) params.set('lastSeq', String(lastSeq))
const ws = new WebSocket(`wss://api.odds-api.io/v3/ws?${params}`)
ws.on('message', (raw) => {
const msg = JSON.parse(raw)
if (msg.type === 'resync_required') {
needsRebuild = true
return ws.close()
}
if (msg.seq > lastSeq) lastSeq = msg.seq
const key = `${msg.id}:${msg.bookie}`
if (msg.type === 'created' || msg.type === 'updated') board.set(key, msg.markets)
if (msg.type === 'deleted' || msg.type === 'no_markets') board.delete(key)
if (msg.type === 'score') console.log(msg.id, msg.scores.home, msg.scores.away)
})
ws.on('error', (err) => console.error('WebSocket error:', err.message || err.code))
ws.on('close', async () => {
if (needsRebuild) lastSeq = await rebuildFromRest()
setTimeout(connect, 2000)
})
}
// Replay was not possible: reload the subscription from REST and resume from its seq
async function rebuildFromRest() {
for (let attempt = 1; ; attempt++) {
try {
const params = new URLSearchParams({ apiKey, eventIds, bookmakers, markets, includeSeq: 'true' })
const res = await fetch(`${API}/odds/multi?${params}`)
const seq = Number(res.headers.get('X-OddsAPI-Seq'))
if (!res.ok || !(seq > 0)) throw new Error(`snapshot not usable: ${res.status}`)
const events = await res.json()
board.clear()
for (const event of events) {
for (const [bookie, odds] of Object.entries(event.bookmakers)) board.set(`${event.id}:${bookie}`, odds)
}
needsRebuild = false
return seq
} catch (err) {
console.error(`${err.message}, retry ${attempt}`)
await new Promise((resolve) => setTimeout(resolve, Math.min(30000, 1000 * 2 ** attempt)))
}
}
}
connect()# pip install websockets
import asyncio
import json
import os
from urllib.parse import urlencode
import websockets
params = urlencode({
"apiKey": os.environ["ODDS_API_KEY"],
"markets": "ML,Spread,Totals",
"leagues": "england-premier-league",
"status": "live",
})
async def main():
async with websockets.connect(f"wss://api.odds-api.io/v3/ws?{params}") as ws:
async for message in ws:
msg = json.loads(message)
if msg["type"] == "updated":
for market in msg["markets"]:
print(msg["id"], msg["bookie"], market["name"], market["odds"][0])
asyncio.run(main())圧縮は必須です
クライアントは接続時にpermessage-deflateを提示する必要があります。帯域を3〜5分の1に削減でき、圧縮なしの接続はHTTP 426で拒否されます。ほとんどのライブラリは自動で対応しています:
| プラットフォーム | ライブラリ | 圧縮 |
|---|---|---|
| Node.js | ws | デフォルトで有効 |
| ブラウザ | ネイティブのWebSocket | デフォルトで有効 |
| Python | websockets | デフォルトで有効 |
| Python | websocket-client | 非対応。websocketsを使用 |
| PHP | amphp/websocket-client | デフォルトで有効(zlibが必要) |
| PHP | textalk/websocket | 非対応。amphp/websocket-clientを使用 |
| Go | gorilla/websocket | EnableCompression = trueを設定 |
| .NET | ClientWebSocket | DangerousDeflateOptionsを設定(.NET 6以降) |
メッセージの種類
すべてのメッセージはtypeを持つJSONオブジェクトです。オッズのメッセージは/v3/oddsエンドポイントと同じ形式なので、1つのパーサーで両方を処理できます。
created- 購読中のイベントをブックメーカーが追加した
updated- オッズが変化した。そのイベントとブックメーカーについて、購読中マーケットの現在の全データを含みます
deleted- イベントが削除された、またはそのブックメーカーが停止した
no_markets- イベントは掲載中だが、購読中のマーケットがどれも開いていない
score- ライブスコアが変化した(scoresチャンネル)
status- イベントがpending、live、settled、cancelledのいずれかに変わった。現在のスコア付き(statusチャンネル)
clock- 時計が進行中のライブイベントごとに、約30秒間隔で送られる試合時計のスナップショット
マージせず、常に置き換える
停止中のマーケットはフラグで示されるのではなく、次のupdatedメッセージから外れます。メッセージを受け取るたびに、そのイベントとブックメーカーの保持データを上書きすれば、取り下げられた価格はアプリから自然に消えます。
{
"type": "updated",
"seq": 482917,
"timestamp": 1723992773,
"id": "63017989",
"bookie": "Bet365",
"markets": [
{
"name": "ML",
"updatedAt": "2026-11-08T15:42:10Z",
"odds": [{ "home": "1.85", "draw": "3.25", "away": "2.10" }]
},
{
"name": "Totals",
"updatedAt": "2026-11-08T15:42:10Z",
"odds": [{ "hdp": 2.5, "over": "1.85", "under": "2.10" }]
}
]
}チャンネルとフィルター
channelsで受け取るストリームを選び、クエリパラメータで絞り込めば、表示するイベントだけを受信できます。leaguesとeventIdsは併用できません。
チャンネル
odds- デフォルト。アカウントで選択したブックメーカーの
created、updated、deleted、no_markets scores- スコアが変わるたびの
scoreメッセージと、clockスナップショット status- pending、live、settled、cancelledを知らせる
statusメッセージと、clockスナップショット
フィルター
markets- oddsチャンネルでは必須。
ML,Spread,Totalsのように最大40個のマーケット名 sportfootball,basketballのように最大10競技leaguesengland-premier-leagueのように最大20個のリーグslugeventIds- 最大50個のイベントID
status- インプレーのみなら
live、プレマッチのみならprematch
channelsは許可リストです。オッズとスコアを一緒に受け取るにはchannels=odds,scores,statusを送ります。channels=scores,statusだけを指定するとオッズは届かなくなります。
取りこぼしのない再接続
ネットワークは切れるものです。すべてのオッズメッセージにはseq番号が付いており、フィードはこれを使って、再接続後に最初からやり直すことなく差分を追いつかせます。
- 1
最後のseqを保持する
受信した中で最大の
seqを保存します。1つの接続内でseqは増える一方ですが、全購読者で共有されているため、番号が飛んでも欠落ではありません。 - 2
lastSeqを付けて再接続する
接続URLに
lastSeqを追加します。切断中に変化したすべてのイベントとブックメーカーの最新状態を受け取り、その後ライブストリームが続きます。 - 3
ライブに戻ったことを知る
replayComplete=trueを追加すると、追いつきが完了した時点でreplay_completeメッセージが1件届きます。リプレイ用データは最大24時間保持されます。 - 4
指示があれば再構築する
正常なリプレイができない場合は
resync_requiredが届きます。includeSeq=trueを付けて/v3/oddsからスナップショットを取得し、X-OddsAPI-Seqヘッダーのseqで再接続してください。
各APIキーで同時に2本の接続を維持できます(例:プライマリとスタンバイ)。追加の接続はご相談に応じます。
ライブスコアと試合時計
インプレーオッズはスコアと並べてこそ意味を持ちます。同じ接続でscoresチャンネルとstatusチャンネルを購読するか、RESTで現在の状態を取得してください。
- GET /v3/events/live
- 現在進行中のすべてのイベントを、スコアと
clockオブジェクト付きで返します。競技で絞り込み可能 - clock
minute、playedSeconds、period、running、statusDetailに加え、サッカーではアディショナルタイム、テニスではサーブ権- ピリオド別スコア
- ハーフ、クォーター、セット、延長、PK戦を、全競技共通のキーで提供
- 時計の進行表示
runningがtrueの間はplayedSecondsからカウントアップし、スナップショットを受け取るたびにサーバーの値にリセットします
RESTによるフォールバック:/oddsと/odds/updated
すべてのプランにREST APIが含まれ、同じライブオッズを提供します。スナップショットの取得や、ソケットが使えない環境でのポーリングに利用できます。
- GET /v3/odds
- 選択したブックメーカーの1イベント分の完全なスナップショット。
/v3/odds/multiなら1リクエストで最大10イベントを取得できます - GET /v3/odds/updated
- 真の差分です。
since以降に変化したマーケットだけを返します(指定できるのは最大90秒前まで)。レスポンスヘッダーX-Next-Sinceを次の呼び出しのsinceに渡せば、欠落なく続けられます - includeSeq=true
- 現在のストリーム位置を
X-OddsAPI-Seqとして付加します。これにより、更新を取りこぼすことなくRESTのスナップショットからWebSocketへ移行できます
差分ではマーケットの取り下げを知ることができないため、ポーリングするクライアントは時々/v3/oddsでイベントを再取得してください。WebSocketでは代わりにdeletedとno_marketsが届きます。
ライブオッズフィードの対象範囲
- ブックメーカー
- 450以上。Bet365、DraftKings、FanDuel、BetMGM、Betfair Exchangeのほか、ヨーロッパ、アジア、オーストラリア、中南米の地域ブックメーカーを含みます
- 競技
- 34競技。サッカー、バスケットボール、テニス、アメリカンフットボール、野球、アイスホッケー、クリケット、eスポーツなど
- インプレー
- インプレーでオッズを出すすべてのブックメーカーのライブオッズを、約1秒ごとに更新
- マーケット
- マネーライン、スプレッド、トータル、アジアンハンディキャップ、コーナー、カード、選手プロップなど(ブックメーカーにより異なります)
- レイテンシ
- WebSocketでプッシュされる更新は<150ms
- 形式
/v3/oddsと同じJSONを、WebSocketとRESTで提供
プランとWebSocketアクセス
すべての有料プランに、ライブオッズを含むREST APIが付いています。WebSocketストリーミングは、どのプランにも追加できるアドオンです。
- アドオンの料金はプラン料金と同額で、合計はプラン料金の2倍です(月払い・年払いとも)
- ストリームにはアカウントで選択したすべてのブックメーカーが含まれるため、利用できるブックメーカー数はプランで決まります
- スコア、ステータス、試合時計も同じ接続で届きます
- いつでも月単位で解約できます
ライブオッズAPIのよくある質問
ライブオッズAPIとは何ですか?
ライブオッズAPIは、進行中のイベントの現在のベッティングオッズをJSONでコードに提供し、ブックメーカーが価格を動かすたびに最新の状態に保ちます。Odds-API.ioは450以上のブックメーカーのライブおよびプレマッチオッズをWebSocketで配信し、同じオッズをRESTでも提供しています。
ライブオッズの速度はどのくらいですか?
WebSocketの更新は<150msのレイテンシで届き、インプレーオッズは約1秒ごとに更新されます。RESTのポーリングではさらにポーリング間隔の分だけ遅れるため、インプレー向けアプリにはWebSocketをおすすめしています。
WebSocket接続が切れた場合はどうなりますか?
最後に受信したseqをlastSeqパラメータに指定して再接続してください。サーバーは切断中に変化したすべての最新状態を再送し(最大24時間の切断まで対応)、その後ライブ配信を続けます。正常なリプレイができない場合はresync_requiredが送られるので、RESTのスナップショットから再構築してください。
どのWebSocketクライアントを使えますか?
必須のpermessage-deflate圧縮に対応したクライアントであれば利用できます。Node.jsのws、ブラウザのWebSocket、Pythonのwebsocketsはデフォルトで対応しています。Pythonのwebsocket-clientとPHPのtextalk/websocketは対応しておらず、HTTP 426で拒否されます。Goではgorilla/websocketでEnableCompressionを設定してください。
フィードにライブスコアと試合時計は含まれますか?
はい。接続URLにchannels=odds,scores,statusを追加すると、オッズと並んでスコアの変化、ステータスの変化、約30秒ごとの試合時計のスナップショットを受け取れます。GET /v3/events/liveでも同じスコアと時計をRESTで取得できます。
WebSocketアクセスはどうすれば利用できますか?
有料プランを選び、WebSocketストリーミングを追加してください。料金はプラン料金と同額で、アカウントで選択したすべてのブックメーカーを配信し、APIキーごとに2本の接続が使えます。ライブオッズや/v3/odds/updatedを含むREST APIは、すべてのプランに含まれます。
今すぐライブオッズを配信
プランを選んでWebSocketストリーミングを追加し、1本の接続を開くだけで、フィード上の450以上のブックメーカーのライブオッズを受け取れます。
いつでも解約可能 · RESTとWebSocketで同じJSON