Short answer: To use the Interactive Brokers API in Python, install the official ibapi client from IBKR's installer rather than from pip, run TWS or IB Gateway with Read-Only API turned off, and subclass EClient and EWrapper with the message loop on its own thread. As read on 5 November 2026 the Latest release is 1052.01, needs Python 3.11 or newer and pins protobuf 7.36.2, while pip install ibapi still delivers 9.81.1.post1 from December 2020.
Everything below was read or run on 5 November 2026 with the official 1052.01 client on Python 3.11.3. No IBKR account was available, so the code talked to a synthetic server on 127.0.0.1, and the testing section says what that leaves unproven. Wrappers are compared in ib_insync vs ib_async, and the choice of API in IBKR Web API vs TWS API.
Start on paper. This guide contains no strategy, recommends no broker and has no referral links, and automated trading can lose money. Rules depend on your country and API access varies by account type. For readers in Turkey, separate rules apply to leveraged FX with foreign firms: the Capital Markets Board said on 23 February 2023 (opens in a new tab) that residents may trade leveraged products only through firms it has authorized (Law No. 6362 (opens in a new tab) Articles 99(4) and 109(2)).
Interactive Brokers API download: why pip is the wrong start
IBKR distributes the client only through its own installers, so pip gives you a package from December 2020 that the broker does not support.
| Source | Version | Released | Protobuf pin | License |
|---|---|---|---|---|
| pip install ibapi | 9.81.1.post1 | 6 December 2020 | None | IB API Non-Commercial or Commercial, per PyPI |
| IBKR Stable | 1050.02 | 9 September 2026 | protobuf==5.29.5 | GPL-3.0-or-later |
| IBKR Latest | 1052.01 | 30 October 2026 | protobuf==7.36.2 | GPL-3.0-or-later |
IBKR says (opens in a new tab) pip and other online repositories are not hosted, endorsed or supported by it; installers are on its API Software page (opens in a new tab). Two statements did not match what shipped:
- Python in Stable. That page says only Latest adds the Python API, yet the Stable Linux and Windows installers, unpacked as data, both contain source/pythonclient.
- Protobuf. The interpreter page (opens in a new tab) describes 5.29.3 and calls version warnings cosmetic. With 1052.01, protobuf 5.29.5 or 6.33.1 raised VersionError on import, so keep one virtual environment per release.
How to install the TWS API for Python and set up the Gateway
Run IBKR's installer, install the pythonclient folder into a fresh virtual environment, then enable the API in TWS or IB Gateway.
python -m venv ibkr-env # activate it next
cd "C:\TWS API\source\pythonclient" # your folder; its name can include the version
python -m pip install .
python -m pip show ibapiOn 5 November 2026 pip reported Successfully installed ibapi-1052.1 protobuf-7.36.2. That test read the folder out of the Windows installer without running it; all 247 files match the Linux copy.
| Setting | Value | Why |
|---|---|---|
| Enable ActiveX and Socket Clients | On in TWS | Needed for socket clients; IBKR says it is automatic in IB Gateway |
| Read-Only API | Off | On by default; blocks every API order |
| Socket port | 7496 TWS live, 7497 TWS paper, 4001 Gateway live, 4002 Gateway paper | Editable defaults; the login behind the port decides live or paper |
| Client ID | Unique, up to 32 per TWS or IB Gateway | Error 326 if taken; orders stay bound to the ID that sent them |
One trap: code comments on IBKR's placing orders page (opens in a new tab) list 7946 and 7947, while its Campus lesson (opens in a new tab) and sync documentation give 7496 and 7497.
EClient and EWrapper: the threading model and how it fails
EClient sends requests, EWrapper receives callbacks, and connect() starts a reader thread (opens in a new tab) filling a queue that run() must drain, so run() goes on a thread of your own.
The nextValidId callback (opens in a new tab) marks the connection as ready, and IBKR warns that earlier calls may be dropped. These ran on 5 November 2026:
| Case | What happened | What to do |
|---|---|---|
| Nothing listening on the port | connect() raised nothing; error() got 502; run() returned at once | Wait for nextValidId, not for an exception |
| error() with the signature from tutorials older than TWS API 10.33 | TypeError in the message loop; the loop ended and the client disconnected | Accept errorTime as the second argument |
| time.sleep(1), as in IBKR's quick start, with a 1.5-second handshake | nextOrderId was still None | Wait on a threading.Event set in nextValidId |
| Program ended without disconnect() | Still running when killed after 10 seconds: the reader thread is not a daemon | Call disconnect() in a finally block |
Since TWS API 10.40, Python also has TWSSyncWrapper (opens in a new tab), which returns values directly from calls. Its order timeout (opens in a new tab) covers only the wait for a reply, so a timeout does not mean the order was not sent.
An IBKR API Python example: connect, reconcile, look up, place
This sample waits for nextValidId, confirms existing state before any order, looks the contract up and places one limit order only on a single match; it refuses the default live ports.
XYZ and the order values are placeholders, not suggestions. It ran unchanged with IB_PORT pointing at the synthetic server, which replayed an earlier execution and sent duplicate status and fill messages on purpose.
import os, threading, time
from decimal import Decimal
from ibapi.client import EClient
from ibapi.contract import Contract
from ibapi.execution import ExecutionFilter
from ibapi.order import Order
from ibapi.wrapper import EWrapper
PORT = int(os.environ.get("IB_PORT", "4002")) # IB Gateway paper default
if PORT in (7496, 4001):
raise SystemExit("live port: this sample is for paper sessions only")
NOTICES = {2104, 2106, 2158} # farm status, not failures
class App(EWrapper, EClient):
def __init__(self):
EWrapper.__init__(self)
EClient.__init__(self, wrapper=self)
self.ready, self.synced, self.looked_up = (threading.Event() for _ in "abc")
self.next_id, self.matches, self.todo = None, [], 0
self.status, self.fills, self.positions = {}, {}, {}
def nextValidId(self, orderId):
self.next_id = orderId
self.ready.set()
def contractDetails(self, reqId, contractDetails):
self.matches.append(contractDetails.contract) # one call per match
def contractDetailsEnd(self, reqId):
self.looked_up.set()
def openOrder(self, orderId, contract, order, orderState):
print("open", orderId, contract.symbol, order.action, order.totalQuantity)
def orderStatus(self, orderId, status, filled, remaining, avgFillPrice, permId,
parentId, lastFillPrice, clientId, whyHeld, mktCapPrice):
if not self.status.get(orderId) == (status, filled): # duplicates are normal
self.status[orderId] = (status, filled)
print("status", orderId, status, "filled", filled, "left", remaining)
def execDetails(self, reqId, contract, execution):
key = execution.execId.rsplit(".", 1)[0] # single-leg: a correction changes the last part
if not self.fills.get(key, ("",))[0] == execution.execId:
self.fills[key] = (execution.execId, execution.shares)
print("fill", execution.execId, execution.side, execution.shares)
def position(self, account, contract, position, avgCost):
self.positions[contract.conId] = position
def error(self, reqId, errorTime, errorCode, errorString, advancedOrderRejectJson=""):
if errorCode not in NOTICES:
print("error", reqId, errorCode, errorString)
if errorCode == 200:
self.looked_up.set()
def sync(self):
self.todo = 3
self.synced.clear()
self.reqAllOpenOrders()
self.reqPositions()
self.reqExecutions(9001, ExecutionFilter())
def done(self, *_):
self.todo -= 1
if self.todo == 0:
self.synced.set()
openOrderEnd = positionEnd = execDetailsEnd = done
app = App()
app.connect("127.0.0.1", PORT, clientId=7)
threading.Thread(target=app.run, daemon=True).start()
try:
if not app.ready.wait(10):
raise SystemExit("no nextValidId: check the port and the API settings")
app.sync()
if not app.synced.wait(10):
raise SystemExit("state not confirmed: no new orders")
print("synced: fills", len(app.fills), "positions", len(app.positions))
c = Contract()
c.symbol, c.secType, c.exchange, c.currency = "XYZ", "STK", "SMART", "USD" # placeholder
app.reqContractDetails(1, c)
if app.looked_up.wait(10) and len(app.matches) == 1:
o = Order()
o.action, o.orderType, o.totalQuantity, o.lmtPrice, o.tif = "BUY", "LMT", Decimal(10), 10.0, "DAY"
order_id, app.next_id = app.next_id, app.next_id + 1
app.placeOrder(order_id, app.matches[0], o)
time.sleep(2)
else:
print("no order:", len(app.matches), "matches")
finally:
app.disconnect()Output of three runs:
--- known contract
fill 0001f4e8.69ff.01.01 BOT 3
synced: fills 1 positions 0
open 101 XYZ BUY 10
status 101 Submitted filled 0 left 10
fill 0001f4e8.6a1b.01.01 BOT 4
status 101 Submitted filled 4 left 6
--- unknown contract, after the same sync lines
error 1 200 No security definition has been found for the request
no order: 0 matches
--- two matching contracts, after the same sync lines
no order: 2 matchesThe duplicates did not reach the log; an unknown symbol or two matches sent no order. A fourth run sent a corrected execId ending in .02, and it replaced the earlier entry.
openOrder, orderStatus and execDetails: what each one tells you
Treat execDetails as the record of fills, orderStatus as a hint that can repeat, and order IDs as something you manage yourself.
| Item | What IBKR says | Consequence |
|---|---|---|
| orderStatus (opens in a new tab) | Called on each change; duplicates are common | Compare with the last value before acting |
| execDetails (opens in a new tab) | One execId per partial fill; a correction changes the digits after the final period; combo fills can add a fifth segment. reqExecutions covers the current day by default | Key fills on execId; treat corrections as replacements; keep your own ledger |
| openOrder (opens in a new tab) | Orders are bound to the client ID that sent them | Only that client can modify them |
| nextValidId (opens in a new tab) | Sent on connect; the sequence persists between sessions and must rise | Increment locally; error 103 means a duplicate ID |
Error 200 means no match or an ambiguous one, and IBKR's error list suggests setting the primary exchange for stocks and the multiplier for futures. Wider order-state design is in trading bot development and broker API integration.
Pacing and reconnecting without doubling orders
Stay under your request budget, and after any disconnect confirm orders, positions and executions before sending anything new.
IBKR's limit (opens in a new tab) is your market data lines divided by 2 per second. Above it, a setting decides the result: error 100, with the session ended after three breaches, or automatic pacing. Small bars have separate limits (opens in a new tab), including 60 requests in ten minutes.
Hypothetical example. At the default 100 lines the limit is 50 requests per second, so 80 market data requests at startup need at least 1.6 seconds. Sent as one burst, they can trigger error 100.
TWS and IB Gateway are designed to restart daily (opens in a new tab), so disconnects are routine. connectionClosed (opens in a new tab) fires when the socket goes; error 1100 means TWS lost IBKR's servers, 1101 restored with market data requests lost, 1102 restored with them kept. The danger is a retry: with order resubmission (opens in a new tab), on by default, TWS resends orders it received while disconnected once connectivity returns, so a program that also resends can double them.
How to test without touching an account
Test callback handling on synthetic messages first, then have a person with a paper login run the same code, and only then consider going live.
What ran: the official client's handshake, reader thread and decoder, contract lookup, order encoding, duplicate handling and the failure cases. What did not run: a real TWS or IB Gateway, a login, IBKR's samples in samples/Python/Testbed, real rejections, restarts, pacing or fills.
For the paper run, record one order from submission to fill, stop IB Gateway with an order open, restart, and check that sync() matches what TWS shows. IBKR warns that paper execution may differ from live (opens in a new tab), and tests show how software behaves, not future results.
Licenses for commercial builds and how to hire a Python IBKR API developer
The client became GPL software in TWS API 10.49, so check distribution before you ship it, and hire on evidence of failure handling rather than on a demo.
The changelog of 3 August 2026 (opens in a new tab) announced the GPL, and the installers carry GPL version 3 text. You can run and change it privately; if you distribute a program containing it, which can include handing code to a client, the license's copyleft terms apply to that distribution. IBKR's download page also prohibits passing market data to third parties through the API without its prior written approval; see stock market data API. This is not legal advice.
When hiring, ask for:
- A paper-session log of one order to fill, including a reconnect
- The rule for repeated orderStatus messages and corrected execIds
- How the pinned API and protobuf versions get upgraded
- Who holds the login: you, with a limited-permission user for the developer
Disclosure: Fincode sells trading software work in general, so this section is not neutral. It builds from rules the customer wrote and never supplies strategies. Its general page is exchange API integration; naming IBKR here does not mean Fincode integrates it or has built on it.
Frequently asked questions
Can I install the Interactive Brokers API with pip?
pip install ibapi gives 9.81.1.post1 from December 2020, which IBKR does not support. Use IBKR's installer, then run pip install . in its source/pythonclient folder, as checked on 5 November 2026.
Do I need TWS running to use the Python API?
Yes, TWS or IB Gateway must be running and logged in, and IBKR does not support running either without a GUI.
Why does my program never exit?
The client's reader thread is not a daemon, so the process stays alive until you call disconnect(). Put that call in a finally block.
Is there a simpler way than callbacks?
IBKR's TWSSyncWrapper, since TWS API 10.40, returns values directly.
Sources
- IBKR TWS API: TWS API Download (distribution statement) (opens in a new tab)
- IBKR: API Software download page (Stable 1050.02, Latest 1052.01) (opens in a new tab)
- IBKR TWS API: Requirements (minimum language versions) (opens in a new tab)
- IBKR TWS API: Update The Python Interpreter (opens in a new tab)
- IBKR TWS API: Placing Orders quick start (opens in a new tab)
- IBKR Campus: Installing and Configuring TWS for the API (Read-Only default, ports) (opens in a new tab)
- IBKR TWS API: Next Valid ID (opens in a new tab)
- IBKR TWS API: Order Status (opens in a new tab)
- IBKR TWS API: Execution Details (opens in a new tab)
- IBKR TWS API: Pacing Limitations (opens in a new tab)
- IBKR TWS API: Intelligent Order Resubmission (opens in a new tab)
- IBKR TWS API: Error Codes (opens in a new tab)
- IBKR TWS API: Changelog (GPL from 10.49, errorTime in 10.33) (opens in a new tab)
- PyPI: ibapi 9.81.1.post1 (opens in a new tab)
- SPK (Capital Markets Board): Notice on unauthorized leveraged transactions, 23 February 2023 (Turkish) (opens in a new tab)
This guide is general information about software and markets, not investment, legal or tax advice. Automated trading can lose money, past or backtested performance does not guarantee future results, and the rules that apply depend on your jurisdiction and your exchange's terms. Sources, checks and corrections: see the editorial policy.