Skip to main content
World Cup 2026 View Guide
Football Bet Odds logo

Football Bet Odds

Betfair API Guide 2026: Authentication, Betting, and Rate Limits

Betfair Exchange API guide showing Python code for authentication, market data retrieval, and automated bet placement

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. 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. 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. 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. 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. 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.crt file. Keep the client-2048.pem private 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)

import 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.

from 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 placeOrders endpoint 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)

from 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)

from 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}"] = True

Error 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

import 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 None

Common 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.

"""
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.

Last updated: · by Football Bet Odds Editorial Team

Affiliate disclosure: Some links are affiliate links. We may earn a commission at no cost to you. Editorial policy.