Betfair API Guide 2026: Authentication, Betting, and Rate Limits
Quick Answer
The Betfair Exchange API is free, officially supported, and documented at developer.betfair.com. Get your API key from the Developer Portal, authenticate using certificate login for bots, and use betfairlightweight or Flumine in Python to place bets programmatically. Rate limit: 1,000 requests/hour. Use the Streaming API for real-time data.
The Betfair Exchange API is the foundation of every serious betting automation project. This guide covers everything from getting your first API key to placing bets programmatically — with working Python code at every step.
The Betfair Exchange API is free to use for personal accounts, supports full bet placement automation, and is documented at developer.betfair.com. It is the only major bookmaker API that explicitly permits automated betting in its terms of service.
Getting Your API Key
Betfair provides two types of API keys: a delay key (free, 1-second data delay, available immediately) and a live key (real-time data, requires a funded account with at least one bet placed). For production automation, you need the live key.
- 1 Register at betfair.com. Create a Betfair Exchange account and complete identity verification. You need a verified account to access the Developer Portal.
- 2 Go to the Developer Portal. Navigate to developer.betfair.com and log in with your Betfair credentials. This is separate from the main Betfair site.
- 3 Create an application. Click "My API Access" then "Create Application". Give it a descriptive name (e.g., "football-arb-bot"). You receive your API key immediately.
- 4 Note both keys. You get a delay key (for testing) and a live key (for production). Store both securely — treat them like passwords. Never commit them to version control.
- 5 Fund your account and place one bet. To activate the live key, you need a funded account with at least one bet placed. A —1 bet on any market is sufficient.
Authentication: Certificate vs SSOID
The Betfair API supports two authentication methods. For automated scripts, certificate authentication is the correct choice. SSOID (interactive login) is for manual sessions only.
Certificate authentication (recommended for bots)
Certificate authentication uses a self-signed SSL certificate to authenticate non-interactive logins. Your script logs in automatically without requiring a browser or manual input. This is the only reliable method for long-running automation scripts.
Step 1: Generate a self-signed certificate
# Generate private key + self-signed certificate (valid 10 years) openssl req -x509 -nodes -days 3650 \ -newkey rsa:2048 \ -keyout client-2048.pem \ -out client-2048.crt # You'll be prompted for certificate details. # Country, organisation etc. can be anything — Betfair doesn't validate them.Step 2: Upload the certificate to Betfair
Go to developer.betfair.com, navigate to your application, and upload the
client-2048.crtfile. Keep theclient-2048.pemprivate key on your server — never share it.Step 3: Authenticate in Python
import betfairlightweight trading = betfairlightweight.APIClient( username="your_betfair_username", password="your_betfair_password", app_key="your_live_api_key", certs="/path/to/certs/", # folder containing client-2048.pem and client-2048.crt ) trading.login() print("Logged in. Session token:", trading.session_token)SSOID (interactive login — not for bots)
SSOID is a session token obtained via the standard Betfair login flow. It expires after 4 hours of inactivity and requires manual re-authentication. Use it for testing and exploration, not for production scripts. Certificate auth handles session renewal automatically.
Reading Market Data
The Betfair API provides two ways to read market data: the REST API (polling) and the Streaming API (WebSocket push). For real-time automation, always use the Streaming API.
Listing football markets (REST API)
pythonimport betfairlightweight from betfairlightweight.filters import market_filter trading = betfairlightweight.APIClient(...) trading.login() # List all Premier League Match Odds markets markets = trading.betting.list_market_catalogue( filter=market_filter( event_type_ids=["1"], # 1 = Football competition_ids=["10932509"], # Premier League competition ID market_types=["MATCH_ODDS"], market_countries=["GB"], ), market_projection=["COMPETITION", "EVENT", "MARKET_START_TIME", "RUNNER_DESCRIPTION"], max_results=50, ) for market in markets: print(f"{market.event.name} — {market.market_name} — {market.market_start_time}") for runner in market.runners: print(f" {runner.runner_name}")Real-time odds via Streaming API
The Streaming API pushes market updates via WebSocket. It does not count against the 1,000 requests/hour rate limit. Flumine uses the Streaming API internally — this is one of the main reasons to use Flumine over raw API calls.
pythonfrom flumine import Flumine, clients from flumine.streams.datastream import DataStream import betfairlightweight trading = betfairlightweight.APIClient( username="your_username", password="your_password", app_key="your_live_key", certs="/path/to/certs/", ) trading.login() # Flumine uses the Streaming API automatically framework = Flumine(client=clients.BetfairClient(trading)) # Add a strategy that processes streaming updates class OddsMonitor(flumine.BaseStrategy): def process_market_book(self, market, market_book): for runner in market_book.runners: if runner.ex.available_to_back: best_back = runner.ex.available_to_back[0].price print(f"{runner.selection_id}: best back = {best_back}") framework.add_strategy(OddsMonitor( market_filter={"event_type_ids": ["1"]}, )) framework.run()Placing Bets Programmatically
The Betfair API's
placeOrdersendpoint places back and lay bets. You specify the market ID, selection ID, side (BACK or LAY), price, and size.Placing a back bet (raw API)
pythonfrom betfairlightweight.resources.bettingresources import ( PlaceInstruction, LimitOrder ) # Place a —10 back bet on selection 12345 at odds 2.0 instructions = [ PlaceInstruction( order_type="LIMIT", selection_id=12345, side="BACK", limit_order=LimitOrder( size=10.0, price=2.0, persistence_type="LAPSE", # Cancel if unmatched at market start ), ) ] result = trading.betting.place_orders( market_id="1.234567890", instructions=instructions, customer_ref="my-bet-001", # Optional: your own reference ) for order in result.instruction_reports: print(f"Status: {order.status}") print(f"Bet ID: {order.bet_id}") print(f"Average price matched: {order.average_price_matched}")Placing bets via Flumine (recommended)
pythonfrom flumine.order.trade import Trade from flumine.order.order import LimitOrder class FootballBackStrategy(flumine.BaseStrategy): def process_market_book(self, market, market_book): for runner in market_book.runners: if runner.status != "ACTIVE": continue best_back = runner.ex.available_to_back if not best_back: continue price = best_back[0].price # Only back if price is above our threshold if price < 2.5: continue # Check we haven't already placed a bet on this runner if market.context.get(f"bet_placed_{runner.selection_id}"): continue trade = Trade( market_id=market.market_id, selection_id=runner.selection_id, handicap=runner.handicap, strategy=self, ) order = LimitOrder( price=price, size=10.0, persistence_type="LAPSE", ) trade.place_order(order) market.place_order(order) # Mark as placed to avoid duplicates market.context[f"bet_placed_{runner.selection_id}"] = TrueError Handling and Rate Limits
The Betfair API returns structured error codes. Handling them correctly is the difference between a script that runs for months and one that crashes after 20 minutes.
Rate limits
Limit type Value REST API requests 1,000 per hour (standard accounts) Streaming connections 10 concurrent connections Streaming subscriptions 200 markets per connection placeOrders per second 5 requests per second listMarketBook batch size 40 markets per call listRunnerBook batch size 40 runners per call Common error codes and how to handle them
pythonimport betfairlightweight from betfairlightweight.exceptions import ( APIError, LoginError, SessionTokenError, ) import time import logging logger = logging.getLogger(__name__) def safe_place_order(trading, market_id, instructions, max_retries=3): """Place an order with retry logic for transient errors.""" for attempt in range(max_retries): try: result = trading.betting.place_orders( market_id=market_id, instructions=instructions, ) return result except SessionTokenError: # Session expired — re-login and retry logger.warning("Session expired, re-logging in...") trading.login() continue except APIError as e: error_code = e.error_code if error_code == "TOO_MUCH_DATA": # Reduce batch size and retry logger.warning("TOO_MUCH_DATA — reduce batch size") time.sleep(1) continue elif error_code == "RATE_LIMIT_EXCEEDED": # Back off and retry wait = 2 ** attempt # Exponential backoff: 1s, 2s, 4s logger.warning(f"Rate limit exceeded — waiting {wait}s") time.sleep(wait) continue elif error_code == "MARKET_SUSPENDED": # Market suspended — do not retry logger.info("Market suspended, skipping order") return None elif error_code == "INSUFFICIENT_FUNDS": # Not enough balance — stop logger.error("Insufficient funds") raise else: logger.error(f"Unhandled API error: {error_code}") raise logger.error(f"Failed after {max_retries} attempts") return NoneCommon pitfalls
Using the delay key in production
The delay key returns 1-second-old data. For live arb execution, this is too slow. Always use the live key in production.
Polling instead of streaming
Polling listMarketBook every second burns through your 1,000 requests/hour limit in 17 minutes. Use the Streaming API for real-time data.
Not handling session expiry
Betfair sessions expire after 4 hours of inactivity. Your script must catch SessionTokenError and re-authenticate automatically.
Placing duplicate orders
Without deduplication logic, a strategy can place multiple orders on the same runner in the same market. Track placed bets in market.context.
Ignoring LAPSE vs PERSIST
LAPSE orders are cancelled at market start if unmatched. PERSIST orders remain in-play. For pre-match strategies, always use LAPSE.
Not testing in simulation mode
Flumine has a simulation mode (SimulatedBetfairClient) that replays historical data without placing real bets. Always test there first.
The Betfair Streaming API pushes real-time market updates via WebSocket and does not count against the 1,000 requests/hour rate limit. It is the correct approach for any automation that requires live odds monitoring.Full Working Example: Football Value Strategy
This complete example uses Flumine to monitor Premier League Match Odds markets and back selections where the best available back price exceeds a configurable threshold. It includes authentication, error handling, and logging.
python""" football_value_strategy.py Monitors Premier League Match Odds markets and backs selections where the best back price exceeds a minimum threshold. Requirements: pip install flumine betfairlightweight Usage: python football_value_strategy.py """ import logging import flumine from flumine import Flumine, clients from flumine.order.trade import Trade from flumine.order.order import LimitOrder import betfairlightweight # -- Config ---------------------------------------------------- USERNAME = "your_betfair_username" PASSWORD = "your_betfair_password" APP_KEY = "your_live_api_key" CERTS_PATH = "/path/to/certs/" MIN_PRICE = 2.5 # Only back at 2.5 or higher STAKE = 10.0 # —10 per bet MAX_BETS = 5 # Maximum bets per market # -- Logging --------------------------------------------------- logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s", ) logger = logging.getLogger(__name__) # -- Strategy -------------------------------------------------- class FootballValueStrategy(flumine.BaseStrategy): def check_market_book(self, market, market_book): """Return True only for pre-match, open markets.""" if market_book.status != "OPEN": return False if market_book.inplay: return False # Pre-match only return True def process_market_book(self, market, market_book): """Process each market update from the Streaming API.""" bets_placed = market.context.get("bets_placed", 0) if bets_placed >= MAX_BETS: return for runner in market_book.runners: if runner.status != "ACTIVE": continue if not runner.ex.available_to_back: continue best_back_price = runner.ex.available_to_back[0].price best_back_size = runner.ex.available_to_back[0].size # Skip if price below threshold or insufficient liquidity if best_back_price < MIN_PRICE: continue if best_back_size < STAKE: continue # Skip if already bet on this runner runner_key = f"bet_{runner.selection_id}" if market.context.get(runner_key): continue # Place the bet trade = Trade( market_id=market.market_id, selection_id=runner.selection_id, handicap=runner.handicap, strategy=self, ) order = LimitOrder( price=best_back_price, size=STAKE, persistence_type="LAPSE", ) trade.place_order(order) market.place_order(order) market.context[runner_key] = True market.context["bets_placed"] = bets_placed + 1 logger.info( f"Placed back bet: {market.market_id} " f"selection={runner.selection_id} " f"price={best_back_price} size={STAKE}" ) # -- Main ------------------------------------------------------ if __name__ == "__main__": trading = betfairlightweight.APIClient( username=USERNAME, password=PASSWORD, app_key=APP_KEY, certs=CERTS_PATH, ) trading.login() logger.info("Authenticated with Betfair API") framework = Flumine(client=clients.BetfairClient(trading)) strategy = FootballValueStrategy( market_filter={ "event_type_ids": ["1"], # Football "competition_ids": ["10932509"], # Premier League "market_types": ["MATCH_ODDS"], }, max_selection_exposure=STAKE * MAX_BETS, max_order_exposure=STAKE, ) framework.add_strategy(strategy) logger.info("Starting Flumine framework...") framework.run()This is a simplified example for educational purposes. Production strategies require position sizing, CLV tracking, and risk management logic.
Frequently Asked Questions
How do I get a Betfair API key?
Register at betfair.com, then go to the Betfair Developer Portal at developer.betfair.com. Create a new application under "My API Access". You receive a delay key immediately (free, 1-second data delay). A live key requires a funded account with at least one bet placed. Both keys are free.What is the difference between the delay key and the live key?
The delay key returns market data with a 1-second delay. It is free and available immediately. The live key returns real-time data with no delay. It requires a funded Betfair account with at least one bet placed. For production betting automation, you need the live key.What is betfairlightweight?
betfairlightweight is a Python library that wraps the Betfair API. It handles authentication, session management, and request formatting. Install with: pip install betfairlightweight. It is the most widely used Python client for the Betfair API.What is the Betfair API rate limit?
The Betfair API has a rate limit of 1,000 requests per hour on standard accounts. Each API call counts as one request. The Streaming API (WebSocket) does not count against the rate limit - it is the recommended approach for real-time market data.What is certificate authentication vs SSOID?
Certificate authentication uses a self-signed SSL certificate to authenticate non-interactive (bot) logins. It is the recommended method for automated scripts. SSOID (Single Sign-On ID) is a session token obtained via interactive login. Certificate auth is more reliable for long-running scripts because it does not require manual re-authentication.Can I use the Betfair API for free?
Yes. The Betfair API is free to use for personal accounts. You pay standard Betfair commission on winning bets (5% on most markets, lower for premium customers). There is no API subscription fee.What is the Betfair Streaming API?
The Betfair Streaming API is a WebSocket-based API that pushes real-time market updates to your client. It is more efficient than polling the REST API and does not count against the rate limit. It is the recommended approach for live market monitoring and in-play betting automation.