- Why Build on the Betfair API
- Account, App Key and Certificate
- Login and Session Management
- Market Discovery: listEventTypes to listMarketCatalogue
- Reading Prices: listMarketBook
- Placing Orders Safely
- The Streaming API — Why and How
- Rate Limits and Weight
- Error Codes and What They Actually Mean
- Production Architecture Patterns
- Testing Without Burning Real Money
- Designing a Bot That Doesn't Self-Destruct
- Monitoring, Logging and Alerting
- Scaling Beyond One Account
- Premium Charge, T&Cs and Account Health
- Where to Go Next
Why Build on the Betfair API
Three reasons people move from a manual trading platform to writing their own code: speed, repeatability, and analysis. The Betfair API lets a process execute decisions in tens of milliseconds rather than the seconds it takes a human to read a screen and click. It runs the same logic at 03:00 as at 13:00, which matters for in-running markets that span time zones. And it produces an unambiguous structured record of every decision the system made — invaluable for the kind of post-hoc analysis that turns a marginal strategy into a profitable one.
The cost is non-trivial. You will spend two to three weeks getting from "I have credentials" to "I have a stable streaming consumer that has run for 24 hours without dying", and another month getting from "the bot places orders" to "the bot places orders I would have placed myself, only faster". Plan for that runway honestly. If you have not yet read the Betfair API overview and the getting-started developer walkthrough, start there.
This guide assumes you can read Python, are comfortable on a Linux shell, and understand HTTP request/response. You do not need to be a senior engineer; you do need to understand JSON, async I/O, and the basics of TLS.
Account, App Key and Certificate
Three things have to be in place before any code will run.
1. A live Betfair account
API access is gated to verified accounts in good standing. Open one through the standard Betfair signup, complete the document upload, and deposit a small balance (£10 is enough — you only need it for testing). UK and Irish accounts get full API access; some other jurisdictions are restricted. Country availability covers the regional picture.
2. Application key
Log into the Betfair Developer site (developer.betfair.com) using your account credentials, navigate to "Application Keys" and create one. Betfair issues two: a delayed key (free, market data lagged ~1–3 minutes) and a live key (£299 one-off fee, real-time data). Start with the delayed key while you build; you will know within a week whether your strategy needs live data.
3. Login certificate
For non-interactive logins (i.e., a bot), you generate a self-signed X.509 certificate, upload the public part to your account, and use the private part in your login request. The mechanics: openssl genrsa -out client-2048.key 2048, then openssl req -new -x509 -days 365 -key client-2048.key -out client-2048.crt, then upload the .crt via "Security" in your account.
Username, password, app key, and the private key are bearer credentials for an account holding real money. Use environment variables or a secrets vault. Putting them in a public GitHub repo has cost more than one developer their account and their balance.
Login and Session Management
There are two login endpoints. Interactive login (identitysso.betfair.com/api/login) issues a session token after a username/password POST. Certificate login (identitysso-cert.betfair.com/api/certlogin) issues the same token but using the client cert for non-interactive sessions. Production bots use cert login.
A successful login returns a session token valid for either 4 hours (Spain/Italy) or 24 hours (UK/IE/AU). After expiry, calls return INVALID_SESSION_INFORMATION and you must re-authenticate. A robust client:
- Logs in eagerly on startup, stores the token in memory.
- Keeps the session alive by hitting /api/keepAlive every 20 minutes.
- Catches INVALID_SESSION_INFORMATION on any subsequent call and re-logs in once, with backoff.
- Logs every login/logout/keepAlive with timestamp for audit.
A minimal Python cert-login looks like this:
import requests
CERT = ('client-2048.crt', 'client-2048.key')
HEADERS = {'X-Application': APP_KEY, 'Content-Type': 'application/x-www-form-urlencoded'}
DATA = {'username': USERNAME, 'password': PASSWORD}
r = requests.post('https://identitysso-cert.betfair.com/api/certlogin',
data=DATA, cert=CERT, headers=HEADERS, timeout=10)
session_token = r.json()['sessionToken']
Wrap it in a class with a property that refreshes the token on demand. Do not pass it as a constructor argument; tokens are short-lived state.
Market Discovery: listEventTypes to listMarketCatalogue
The discovery hierarchy is: event types (sports) → competitions → events (matches/races) → markets → runners. The four endpoints you need:
- listEventTypes — sports. Returns IDs like 1 (Soccer), 7 (Horse Racing), 2 (Tennis). Static list; cache for the day.
- listCompetitions — competitions within a sport. Returns IDs like Premier League. Refresh hourly.
- listEvents — individual events. Returns each match with a startTime. Refresh every 5–10 minutes during trading hours.
- listMarketCatalogue — markets per event. Returns marketId, marketName, runners. Refresh per event when needed.
The cardinal sin here is calling listMarketCatalogue with no filter — you will pull tens of thousands of markets and burn rate-limit weight for nothing. Always filter by eventTypeIds and a marketStartTime range. Example for "all UK racing markets in the next two hours":
filter_dict = {
"eventTypeIds": ["7"],
"marketCountries": ["GB"],
"marketTypeCodes": ["WIN"],
"marketStartTime": {
"from": now_iso,
"to": now_plus_2h_iso
}
}
Cache aggressively. Market metadata changes rarely; pulling it once per event start is plenty. The expensive call is listMarketBook, which holds the live prices and which you can poll several times per second on a single market.
Reading Prices: listMarketBook
listMarketBook is where price data lives. Pass a marketId (or up to 40 marketIds in one call), receive back runner-by-runner prices, total matched, and book status. The shape of the response per runner includes:
- ex.availableToBack: the back stack — list of {price, size}, best price first.
- ex.availableToLay: the lay stack — same structure.
- ex.tradedVolume: the matched ladder — every price at which volume has been matched. Useful for liquidity heatmaps.
- lastPriceTraded: the most recent match. Often what a casual trader thinks of as "the price".
- totalMatched: the £ value matched on this runner.
The trap: polling listMarketBook at 1 Hz on hundreds of markets in parallel consumes your rate-limit weight fast and gives you stale data because polling fundamentally lags the truth. For anything in-running, you want the streaming API — see the next section. Use listMarketBook for pre-event polling at sensible intervals (every 30 seconds outside the last 15 minutes; every 5 seconds inside it) and switch to streaming the moment the market goes in-running.
Placing Orders Safely
placeOrders is the endpoint that writes to your balance. Treat it with the respect that implies. The minimum required payload:
{
"marketId": "1.234567890",
"instructions": [{
"selectionId": 12345,
"handicap": 0,
"side": "BACK",
"orderType": "LIMIT",
"limitOrder": {
"size": 10.00,
"price": 3.40,
"persistenceType": "LAPSE"
}
}],
"customerRef": "tradeid-2026-05-18-001"
}
Five rules every developer who has lost money on this endpoint will tell you:
- Always set customerRef. If the response is ambiguous (network blip during placement) you can call listCurrentOrders with the customerRef to confirm whether the order made it.
- Validate price against the tick table before sending. Sending price 1.555 returns INVALID_ODDS. Round to a valid tick — see the glossary for the full table.
- Validate size against minimum stake. £2 minimum on most markets; £5 on some Australian markets. Sending £1.95 fails.
- Handle partial fills. The response includes the matched portion and the unmatched remainder. Decide upfront whether to leave it queued, cancel, or modify.
- Use persistenceType deliberately. LAPSE = unmatched portion cancels at off-time. PERSIST = unmatched portion goes to SP. MARKET_ON_CLOSE = the order becomes an SP order from the start. Most of the time you want LAPSE — explicit kills are safer than implicit fills.
Reference reading: building your first bot in Python and building your own trading system.
The Streaming API — Why and How
For anything time-sensitive, streaming is non-negotiable. The streaming API maintains a TCP socket connection to Betfair and pushes delta updates of market state as they happen. Latency drops from "polling round-trip" (typically 80–300 ms) to "socket push" (typically 8–40 ms). That difference is the line between a profitable in-play strategy and one that always arrives second.
Conceptually, you connect to stream-api.betfair.com:443, authenticate with your session token and app key, then subscribe to either a marketSubscription (price data) or an orderSubscription (your own order state). Updates arrive as line-delimited JSON; each message is either an image (full snapshot) or a delta (changes only). You maintain local state by applying deltas to the most recent image.
The hard part is not the protocol — it is the operational reliability. The socket will drop. You will get partial reads. You will hit reconnection rate limits if you reconnect too aggressively. Production-grade clients we have seen all share four traits:
- Exponential backoff on reconnects, capped at 30 seconds.
- Heartbeat monitoring — if no message in 5 seconds, kill the socket and reconnect.
- Separate threads/coroutines for read and write, with a bounded queue between them.
- Idempotent state replay so an unexpected restart re-syncs without missing trades.
If you are new to async I/O patterns, expect this part to take you longer than the rest of the API combined. The official Betfair-provided libraries (betfair-stream-recorder, the community betfairlightweight package) handle most of the protocol grind — use them unless you have a specific reason to roll your own.
Rate Limits and Weight
The Betfair API meters request weight, not request count. Each endpoint has a weight; your account has a per-window allowance. The numbers shift; check the current limits in your developer dashboard. Approximate guideposts as of late 2025:
| Operation | Approx weight | Sensible cadence |
|---|---|---|
| listEventTypes | 1 | Hourly |
| listEvents (filtered) | 2–5 | Every 5 min |
| listMarketCatalogue (filtered) | 5–17 | Per event init |
| listMarketBook (1 market) | 2 | 1 Hz outside last 5 min |
| listMarketBook (40 markets) | ~25 | 0.2 Hz |
| placeOrders / cancelOrders | 1 each | As needed |
| Streaming subscription | fixed channel | Always preferred |
Two operational practices fall out of this: use streaming where latency matters, and batch listMarketBook calls (up to 40 markets per call) where polling is required. A bot that polls one market at a time is using 40× the weight it needs.
Error Codes and What They Actually Mean
The Betfair API returns specific error codes. Knowing the common ones in advance saves hours of head-scratching.
- INVALID_SESSION_INFORMATION: session expired. Re-login.
- INVALID_APP_KEY: app key wrong or revoked. Check the developer dashboard.
- TOO_MUCH_DATA: filter too wide. Narrow your listMarketCatalogue filter.
- INSUFFICIENT_FUNDS: balance does not cover liability. Check exposure model before placing.
- BET_TAKEN_OR_LAPSED / RUNNER_REMOVED / MARKET_NOT_OPEN_FOR_BETTING: race condition between read and write. Re-read state.
- INVALID_BET_SIZE / INVALID_PRICE: request did not validate before send. Fix client-side validation.
- BACK_LIABILITY_LAY_SIDE_NOT_ALLOWED: sending lay instructions on a market mode you cannot trade. Check market type.
- DUPLICATE_BETIDS / DUPLICATE_CUSTOMER_REF: idempotency tripped. You probably retried a successful call.
Treat duplicates as a feature, not a bug — if a network blip makes you uncertain whether placeOrders succeeded, retrying with the same customerRef is safer than blindly re-sending. The duplicate detection catches you.
Production Architecture Patterns
After watching a dozen bots either die or quietly bleed money, four patterns keep coming up in the ones that survive a year of production.
Separation of policy from execution
Strategy code (when to enter, when to exit, how to size) belongs in one module. Execution code (how to talk to Betfair, retries, rate limiting) belongs in another. The interface between them is small: "I want £X at price Y, persistence Z, on market M, runner R". This separation lets you change strategy without touching execution, swap from API REST to streaming without touching strategy, and unit-test strategy with mocked execution.
Immutable event log
Every market data update, every order intent, every order response, every fill — append-only to a log (local file, Kafka, whatever fits). Reconstruct state by replaying the log. This is the single most valuable engineering decision you make. When a strategy behaves unexpectedly, you can replay the exact market state and see why the bot decided what it decided. Without the log you are guessing.
Exposure caps at multiple levels
Per-market exposure cap. Per-strategy exposure cap. Per-account exposure cap. Per-day loss cap. Each level rejects orders that would breach. Belt and braces; you only need one to save the account when the others slip.
Idle == safe
If anything is wrong — auth expired and refresh failing, streaming socket dropped twice in a minute, exposure-cap calculation throws — the system flatten-and-stops. It does not "try its best". A bot that places orders when it does not know the current state of the world is a bot that will eventually self-destruct.
Testing Without Burning Real Money
Live testing on real markets at minimum stakes is fine — but you can do most of the work on synthetic data. Three layers:
- Unit tests: mock the API client, feed canned market-book responses, assert strategy intents. Fast, deterministic, no balance risk.
- Replay tests: record a streaming session to disk (most Python streaming libraries support this), replay it into your strategy off-line. Tests timing-sensitive behaviour without burning real markets.
- Paper trading: wire your strategy to a stub execution layer that logs intents but does not call placeOrders. Run it live for a week alongside real markets. Reconcile what it "would have done" against what the market did.
Only after all three pass should the same bot be turned loose with real placeOrders calls — and start at minimum stakes (£2) for at least 100 trades before scaling.
Designing a Bot That Doesn't Self-Destruct
The painful war stories from running a trading bot rarely come from clever code. They come from edge cases:
- Daylight saving transitions: if you key off local time, your bot will mis-schedule two days a year. Use UTC end-to-end, only convert at the display layer.
- Market suspensions: markets suspend during in-running goal events / horse-racing reformations. Your bot has to handle "the market is suspended and my unmatched order is sitting there" without panicking and re-issuing.
- Postponed events: a tennis match moved to tomorrow. Your strategy was holding a pre-match position. Decide policy: close on postponement, hold across, or human-review.
- Account flags: if Betfair restricts an account for any reason, all order calls fail. Your bot should fail closed (no orders) not fail open (retry forever).
- Clock drift: NTP must be running. Time skew of a few seconds causes "race condition" symptoms that are actually clock issues.
None of these are theoretical. All have cost real money to real developers. Read Betfair automation and bots and automation triggers and rules for cross-checks.
Monitoring, Logging and Alerting
A bot you can not see is a bot you can not trust. Minimum monitoring:
- Per-minute heartbeat: bot writes "alive" to a metric every 60 seconds. Missing heartbeats page you.
- Order success ratio: failed placeOrders per minute. Spike = look immediately.
- Equity curve: running P&L plotted against time. Sharp drops trigger investigation.
- Streaming health: messages-per-second from the stream socket. Drops to zero with the bot still "running" = stale state, kill immediately.
- Exposure heat-map: current open liability by market and runner. Catches "I forgot the bot was holding this overnight" failures.
Tools that work: Prometheus + Grafana if you are running on Linux; a simple SQLite log + a Streamlit dashboard for a one-developer setup; CloudWatch/Datadog if you are on cloud already. Whatever you pick, the heartbeat + equity curve are the minimum bar.
You Need a Verified Account Before You Code
API access only works on verified accounts. Open one, deposit a small balance, generate an app key, and you are ready to start. The hard work begins after.
Open Betfair Account →Scaling Beyond One Account
Most bot developers do not need to scale beyond one account. If you do — because you are running multiple strategies that conflict on the same markets, or because exposure caps are binding your size, or because you operate trading for friends or family — be careful. Betfair's terms of service have strict rules about multiple accounts. Read them. The most common legitimate pattern is a strategy that runs on one master account, with API-driven reporting feeding multiple downstream consumers.
Far more developers run into premium charge first. Once cumulative net profit on an account passes the threshold, Betfair levies an additional charge on top of commission. Bot developers building profitable strategies cross this line earlier than manual traders. Build the math into your forecasts.
Premium Charge, T&Cs and Account Health
Three operational risks you must manage outside the code.
Premium charge: see above. Premium charge explained spells out the formula and the implications for a profitable account.
T&Cs compliance: Betfair's rules on bots are permissive — they are one of the few major operators that explicitly allow API automation — but multiple-account and collusion rules are strict. Read the developer terms once, link them in your project repo, re-read them every six months.
Account health signals: sudden reductions in withdrawal limits, certificates being silently invalidated, unexpected market subscription rejections — these can be early signs of account review. If you see them, contact Betfair customer support; do not just retry harder.
Worked Example: A Pre-Race Lay Bot
To make the abstract concrete, here is an end-to-end sketch of the simplest profitable bot we have run — a pre-race favourite lay across UK horse-racing. The strategy is straightforward: identify the favourite at T-minus-5-minutes, lay it at the current market price, set a stop-loss back-bet 6 ticks longer, and let it run.
Inputs: stake £20, stop tick offset 6, eligible races UK win markets with > £100K matched in the previous race in the same meeting (a liquidity proxy).
Skeleton, Python pseudocode:
# Every 30 seconds
events = list_events(eventTypeIds=[7], marketCountries=['GB'],
marketStartTime={'from': now, 'to': now+timedelta(minutes=15)})
for event in events:
catalogue = list_market_catalogue(eventIds=[event.id], marketTypeCodes=['WIN'])
market = catalogue[0]
book = list_market_book(marketIds=[market.id])
if time_to_off(market) > 360 or time_to_off(market) < 240:
continue # outside 4-6 min window
favourite = sorted(book.runners, key=lambda r: r.last_price_traded)[0]
if favourite.last_price_traded > 4.0:
continue # too long; weak favourite
# Place the lay
place_orders(
market_id=market.id,
selection_id=favourite.id,
side='LAY',
size=20.00,
price=favourite.last_price_traded,
customer_ref=f"prerace-lay-{market.id}-{now}"
)
# Place the stop-loss back at +6 ticks
stop_price = price_plus_ticks(favourite.last_price_traded, 6)
place_orders(
market_id=market.id,
selection_id=favourite.id,
side='BACK',
size=size_to_match_lay_liability(20.00, favourite.last_price_traded, stop_price),
price=stop_price,
persistence='LAPSE',
customer_ref=f"prerace-stop-{market.id}-{now}"
)
Even at this length, you can see the production gaps. What if the order is partially matched? What if the stop fills and the lay does not? What if two races overlap? Each gap is a real bug we have hit, and each requires explicit handling. The lesson: the strategy in pseudocode is 30 lines; the production bot is 1,500 lines, mostly defensive code. Background reading: trading the favourite pre-race and pre-race scalping walk through the manual version of the same idea.
Choosing a Language and Library
You can talk to the Betfair API from any language with a JSON-RPC HTTP client and a TCP socket library — which is every modern language. Practical recommendations by use case:
Python
The defacto standard. betfairlightweight is the most mature community library; it handles auth, REST endpoints, streaming, and the JSON-to-object mapping. Combined with pandas for analysis and FastAPI for any dashboards, Python is the path of least resistance. The downside is the GIL — for very-low-latency multi-market streaming you may hit ceiling.
Node / TypeScript
Strong choice if you live in the JavaScript ecosystem. Event-loop concurrency maps naturally to streaming sockets. Libraries are less mature than Python; expect to write more glue code.
Go
Worth it if latency matters or you are running many concurrent strategies. Concurrent socket handling is simpler than Python's, the deployment binary is single-file, and the type system catches whole categories of bugs Python misses. Community libraries are thin; you will write more of the protocol yourself.
C# / .NET
Betfair publishes official .NET samples and the platform's heritage is .NET. If you have a .NET background, it is the most "official" path. Libraries are well-maintained.
Whichever you pick, structure your project so the API client is replaceable. Strategy code should never import Betfair-specific types — it should consume a thin internal interface. That makes language switches and library upgrades a sane afternoon, not a re-write.
Where to Go Next
You have the developer's map. The strategy work happens elsewhere. Three paths to take from here:
If you want to build a specific strategy
Pick one of scalping, swing trading, or a pre-match strategy. Build the discretionary version first, log six weeks of trades, then automate the high-frequency parts. Strategies that fail manual trading also fail automated.
If you want to deepen the engineering
Read building your own betting model and data, analytics & research. The strongest bot developers are also strong data analysts.
If you want the broader picture
Start with the complete Betfair Exchange guide, then what is Betfair trading, then can you make a living trading Betfair. The API is a tool; the strategy is the work.
Last word: most people who set out to build a Betfair bot abandon the project within six months. The drop-out point is usually around week 8, when the proof-of-concept works but the production work — error handling, monitoring, replay, exposure management — feels disproportionate to the strategy gain. Push through. The bot you ship is almost never the bot you set out to build, but the discipline of shipping is what generates the long-run edge.