To use the Betfair API with Python you need an App Key, a logged-in session token (certificate login is the robust route), and a client library such as betfairlightweight. Once authenticated you call listMarketCatalogue to find markets and listMarketBook to read live prices. Start on the Delayed App Key and tiny stakes before anything automated.
This page contains affiliate links — if you open an account through them we may earn a commission at no cost to you. It never changes our verdict.
This is a hands-on sub of our Betfair API developer's guide. If you have never touched the API before, read how to get a Betfair API key first, because every line below assumes you already have an App Key and a non-interactive certificate set up. The pillar covers the architecture; this page gets you from a blank file to a live market read in about thirty minutes.
I have run Python against the Exchange since the SOAP days and through the move to JSON-RPC. The single biggest time-sink for beginners is not the code — it is the certificate login. We will do it properly so your session does not silently expire mid-script.
- Prerequisites
- Installing the library
- What the API actually is
- Certificate login (the robust way)
- Finding a market
- Reading the live ladder
- Keeping the session alive
- From the desk: my first live read
- Placing and cancelling a test bet
- Errors that waste your afternoon
- Logging a market to CSV
- The full minimal script
- Where to go next
Prerequisites
Before any code, you need four things in place. Miss one and you will spend an hour debugging an authentication wall instead of writing logic.
- A funded Betfair account with the API activated. New to the platform? Start with opening an account.
- An Application Key. Get the Delayed key first — it returns data on a delay and is perfect for learning without risking live prices. Full steps in getting an API key.
- A registered SSL certificate for non-interactive login. This is the part beginners skip and regret.
- Python 3.10+ and a virtual environment. Never install API libraries into your system Python.
Installing the library
You can call the JSON-RPC endpoint with plain requests, but for learning I recommend betfairlightweight — it wraps authentication, certificate login, and the endpoints into clean Python objects. Set up a clean environment and install it:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install betfairlightweight
That single dependency pulls in everything you need. If you would rather understand the raw HTTP underneath — useful when you build your own trading bot later — the pillar shows the bare JSON-RPC calls.
What the Betfair API actually is
Before the code makes sense, hold the right mental model. The Betfair Exchange API is a JSON-RPC interface: you send a JSON request naming a method — listMarketCatalogue, listMarketBook, placeOrders — with a parameters object, and Betfair returns JSON. That is the whole protocol. Everything betfairlightweight does is build those requests for you and parse the responses into Python objects so you are not hand-assembling dictionaries. When you read the developer guide and see raw request bodies, those are the same calls the library wraps.
There are two API surfaces worth knowing. The Betting API handles markets, prices, and orders — the parts in this tutorial. The Accounts API handles your balance, statement, and App Keys. They share a session token but live at different endpoints, and the library exposes them as trading.betting and trading.account. A third surface, the Streaming API, pushes price updates to you over a persistent socket instead of you polling — that is where you go once polling feels slow, but polling is the right place to learn because it is simpler to reason about. Keep this map in your head and the method names stop feeling arbitrary: catalogue to find, book to price, orders to act.
Certificate login (the robust way)
There are two login routes: interactive (username, password, App Key) and non-interactive (certificate-based). Interactive tokens expire and get throttled; certificate login gives you a stable session you can refresh. For anything you intend to leave running, use certificates. Place your client-2048.crt and client-2048.key in a certs/ folder, then:
import betfairlightweight
from betfairlightweight import filters
trading = betfairlightweight.APIClient(
username="YOUR_BETFAIR_USERNAME",
password="YOUR_BETFAIR_PASSWORD",
app_key="YOUR_DELAYED_APP_KEY",
certs="certs/", # folder holding client-2048.crt/.key
)
trading.login()
print("Session token:", trading.session_token[:12], "...")
Never hard-code credentials in a script you might share. Put them in environment variables or a .env file that is git-ignored. The login() call performs the certificate handshake; if it raises a CertsError, your certificate is not registered against the account — go back to the key setup. A successful login prints the first characters of a session token.
Finding a market
Markets are found with listMarketCatalogue, filtered by event type, competition, or time. Here we ask for the next ten UK & Irish horse-racing WIN markets, sorted by start time. Event type 7 is horse racing — the IDs are stable and worth memorising (1 = football, 2 = tennis, 4339 = greyhounds).
market_filter = filters.market_filter(
event_type_ids=["7"], # horse racing
market_countries=["GB", "IE"],
market_type_codes=["WIN"],
)
catalogue = trading.betting.list_market_catalogue(
filter=market_filter,
market_projection=["MARKET_START_TIME", "RUNNER_DESCRIPTION", "EVENT"],
max_results=10,
sort="FIRST_TO_START",
)
for m in catalogue:
print(m.market_id, m.market_start_time, m.event.venue, m.market_name)
That prints a list like 1.245678901 2026-06-01 14:30 Newmarket WIN. Note the market_id — everything price-related keys off it. If you understand market structure poorly, read how to read a Betfair market before going further, because the API returns exactly what the ladder shows.
Reading the live ladder
Prices come from listMarketBook. You pass the market ID and a price projection asking for the best available back and lay prices. This is the call your strategy will hit most often, so understand its shape.
price_filter = filters.price_projection(
price_data=["EX_BEST_OFFERS"],
)
book = trading.betting.list_market_book(
market_ids=[catalogue[0].market_id],
price_projection=price_filter,
)[0]
for runner in book.runners:
backs = runner.ex.available_to_back
lays = runner.ex.available_to_lay
best_back = backs[0].price if backs else None
best_lay = lays[0].price if lays else None
print(runner.selection_id, "back", best_back, "lay", best_lay)
Each runner exposes available_to_back and available_to_lay lists ordered best-first. On the Delayed key these prices lag real time by a minute or so — fine for learning, useless for scalping. You graduate to the Live key only when your code is solid and you understand the rate limits.
Keeping the session alive
This is the detail that separates a script that runs for five minutes from one that runs all afternoon. A Betfair session token goes stale after about four hours, and sooner if Betfair is under load. If your code polls a market in a loop, you must refresh the token or every call past the timeout returns INVALID_SESSION_INFORMATION. The fix is a periodic keep-alive, ideally on its own timer so your main loop never has to think about it.
import time, threading
def keep_session_warm(client, interval=1800):
# refresh the Betfair session every 30 minutes
while True:
time.sleep(interval)
try:
client.keep_alive()
except Exception as e:
print("keep_alive failed, re-logging in:", e)
client.login()
threading.Thread(target=keep_session_warm, args=(trading,), daemon=True).start()
A daemon thread calling keep_alive() every thirty minutes keeps the token fresh while your strategy loop runs uninterrupted. If a refresh ever fails — a dropped connection, a Betfair maintenance window — the except branch re-logs in cleanly rather than crashing. This one pattern prevents the most common "it worked then stopped" complaint from beginners. Wrap your real trading logic in the same defensive mindset before you ever leave it unattended, and pair it with the guard rails in building Betfair bots.
Running the price loop above on a Newmarket 14:30 about an hour before the off, the favourite came back as selection 12345678 with back 3.05 / lay 3.10 — a one-tick spread, exactly what the web ladder showed at the same moment.
The second favourite read back 4.3 / lay 4.4, and a 33/1 outsider showed back 34.0 / lay 38.0 — that fat four-tick-plus spread on the outsider is the API telling you the same thing the ladder does: thin liquidity, do not trade it without a limit order.
The lesson that stuck: the first time the numbers in my terminal matched the numbers on the website tick-for-tick, the whole thing clicked. The API is not a different Betfair; it is the same market, read programmatically. Once you trust that, you stop second-guessing the data and start building logic. I logged that first read to a CSV and still have it.
Placing and cancelling a test bet (carefully)
Only do this on the Live key with a tiny stake, and cancel immediately. The minimum bet on the UK Exchange is £2. We place a back bet far below the market so it will not match, confirm it is live, then cancel it. This proves your write path works without risking a real fill.
# place a 2.00 back at a deliberately unmatched price (e.g. 1000)
order = filters.limit_order(size=2.00, price=1000, persistence_type="LAPSE")
instruction = filters.place_instruction(
order_type="LIMIT",
selection_id=book.runners[0].selection_id,
side="BACK",
limit_order=order,
)
placed = trading.betting.place_orders(
market_id=book.market_id,
instructions=[instruction],
)
bet_id = placed.place_instruction_reports[0].bet_id
print("Placed unmatched bet:", bet_id)
# immediately cancel it
trading.betting.cancel_orders(
market_id=book.market_id,
instructions=[filters.cancel_instruction(bet_id=bet_id)],
)
print("Cancelled.")
Because the price 1000 is far above any real lay, the bet rests unmatched and the cancel removes it. If you ever see status: SUCCESS with a size_matched above zero on a test, you priced it wrong — check the ladder. Automating real orders is a different responsibility level; the safe patterns live in building Betfair bots.
Errors that waste your afternoon
- INVALID_SESSION_INFORMATION — your token expired. Call trading.keep_alive() on a timer, or re-login. Sessions die after about four hours idle.
- CertsError / INVALID_APP_KEY — certificate not registered, or you used the Delayed key where you needed Live (or vice versa).
- TOO_MUCH_DATA — you requested too many markets or too rich a projection in one call. Narrow the filter; respect rate limits.
- Empty available_to_back — not an error. The market simply has no offers at that moment, common pre-off on minor markets.
- Timezones — the API returns UTC. Convert before comparing to a UK race time or you will be an hour out in summer.
For a structured catalogue of failure modes and retry patterns, the dedicated API error handling sub goes deeper than this quick list. The meta-lesson is that almost every beginner error is one of three things: a stale session, a key/certificate mismatch, or a request that asked for too much. When something breaks, check those three before you suspect the library, because the library is rarely the problem. Build a tiny wrapper that catches the API exception, prints the error code, and retries once after a short sleep, and you will resolve the overwhelming majority of hiccups without ever leaving your editor.
Logging a market to CSV for later analysis
Before you trade a single pound, log some markets and study them offline. A few hundred price snapshots tell you more about how a market moves into the off than any tip ever will. This snippet appends the best back/lay of every runner to a CSV each time it polls, which you can later load into pandas for data analysis.
import csv, datetime
def snapshot(client, market_id, path="prices.csv"):
book = client.betting.list_market_book(
market_ids=[market_id],
price_projection=filters.price_projection(price_data=["EX_BEST_OFFERS"]),
)[0]
ts = datetime.datetime.utcnow().isoformat()
with open(path, "a", newline="") as f:
w = csv.writer(f)
for r in book.runners:
back = r.ex.available_to_back[0].price if r.ex.available_to_back else ""
lay = r.ex.available_to_lay[0].price if r.ex.available_to_lay else ""
w.writerow([ts, market_id, r.selection_id, back, lay])
Call snapshot() on a timer from, say, twenty minutes before the off, and you build a price history you actually own. Studying how a favourite firms or drifts in that window is the single most useful homework a new racing trader can do, and it costs nothing but disk space.
The full minimal script, start to finish
Here is the whole thing assembled — login, find the next race, read its ladder — in one runnable file. If this executes and prints prices that match the website, your environment is correct and everything else you build sits on top of it.
import betfairlightweight
from betfairlightweight import filters
trading = betfairlightweight.APIClient(
username="USER", password="PASS", app_key="DELAYED_KEY", certs="certs/")
trading.login()
cat = trading.betting.list_market_catalogue(
filter=filters.market_filter(
event_type_ids=["7"], market_countries=["GB", "IE"],
market_type_codes=["WIN"]),
market_projection=["EVENT", "RUNNER_DESCRIPTION"],
max_results=1, sort="FIRST_TO_START")[0]
book = trading.betting.list_market_book(
market_ids=[cat.market_id],
price_projection=filters.price_projection(price_data=["EX_BEST_OFFERS"]))[0]
print(cat.event.venue, cat.market_name)
for r in book.runners:
b = r.ex.available_to_back[0].price if r.ex.available_to_back else "-"
l = r.ex.available_to_lay[0].price if r.ex.available_to_lay else "-"
print(f" {r.selection_id}: back {b} / lay {l}")
trading.logout()
That is roughly thirty lines for a complete, authenticated, live market read. Everything advanced — streaming, automated execution, dashboards — is an extension of these same calls. Get this running first; resist the urge to automate trades until it is second nature. The whole edifice — streaming, bots, dashboards — rests on these few reliable calls, so it pays to make them boringly solid before you build anything ambitious on top of them.
Where to go next
You can now log in, find markets, read prices, and place/cancel orders — the whole spine of any tool. From here, three directions: stream prices in real time with the Streaming API instead of polling; turn the data into something you can see with a Betfair dashboard; or feed it into data analysis to test an edge before you risk a penny. If JavaScript is more your language, the same ground is covered in the Node.js guide from the pillar. Whatever you build, keep using the Delayed key until the logic is proven.
Automated betting can lose money far faster than manual trading — a logic bug can fire dozens of orders before you notice. Test on the Delayed key, use tiny stakes, add hard stop-loss and max-stake guards, and never run unattended code you do not fully understand. Most traders lose money; automation does not change that. 18+ only.
Have your App Key and certificate ready, then work through the pillar for the full API architecture.
API Developer Guide Open Betfair Account →FAQ
Which Python library is best for the Betfair API? betfairlightweight is the most popular and beginner-friendly choice. It handles certificate login, session management, and wraps the JSON-RPC endpoints in clean Python objects. You can also call the API with the plain requests library if you want to understand the raw HTTP.
Do I need a certificate to use the Betfair API? Not strictly, but you should. Interactive (password) login works for quick tests, but its tokens expire and get throttled. Certificate-based non-interactive login gives a stable, refreshable session that is essential for anything you leave running.
What is the difference between the Delayed and Live App Key? The Delayed key returns market data on a delay of around a minute and is ideal for learning and testing without risk. The Live key returns real-time data and is required for actual trading. Always develop on the Delayed key first.
How do I read back and lay prices from the API? Call listMarketBook with an EX_BEST_OFFERS price projection. Each runner returns available_to_back and available_to_lay lists, ordered best price first. The first element of each is the best currently available price.
Can the Betfair API place bets automatically? Yes, via place_orders, but it carries real risk. A logic bug can fire many orders quickly. Test with unmatched prices on tiny stakes, add stop-loss and max-stake guards, and never run automated betting code you do not fully understand.