# Interactive Brokers API Python: setup, orders, failures

URL: https://fincodelabs.com/en/blog/interactive-brokers-api-python · Publisher: Fincode (Fincode Yazılım ve Teknoloji A.Ş.), Istanbul, Turkey · Category: Stocks, forex and broker APIs · Published: 2026-11-06 · Updated: 2026-11-06

Interactive Brokers API Python guide for ibapi 1052.01: why pip is wrong, ports, threading, order callbacks, pacing and reconnects, tested on a mock socket.

**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](https://fincodelabs.com/en/blog/ib-async-vs-ib-insync), and the choice of API in [IBKR Web API vs TWS API](https://fincodelabs.com/en/blog/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](https://spk.gov.tr/duyurular/basin-duyurulari/2023/izinsiz-kaldiracli-islemlere-iliskin-basin-duyurusu) that residents may trade leveraged products only through firms it has authorized ([Law No. 6362](https://www.mevzuat.gov.tr/mevzuatmetin/1.5.6362.pdf) 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.

**Where ibapi comes from, checked 5 November 2026**

| 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](https://www.interactivebrokers.com/docs/tws-api/doc/download-the-tws-api) pip and other online repositories are not hosted, endorsed or supported by it; installers are on its [API Software page](https://download2.interactivebrokers.com/installers/tws-api/index.html). 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](https://www.interactivebrokers.com/docs/tws-api/doc/unique-configurations/updating-the-python-interpreter) 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.

```bash
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 ibapi
```

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

**Settings under Global Configuration, API (IBKR docs and Campus lesson, read 5 November 2026)**

| 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](https://www.interactivebrokers.com/docs/tws-api/doc/quick-start/placing-orders) list 7946 and 7947, while its [Campus lesson](https://www.interactivebrokers.com/campus/trading-lessons/installing-configuring-tws-for-the-api/) 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](https://www.interactivebrokers.com/docs/tws-api/doc/connectivity/e-reader-thread) filling a queue that run() must drain, so run() goes on a thread of your own.

The [nextValidId callback](https://www.interactivebrokers.com/docs/tws-api/doc/connectivity/establishing-an-api-connection) marks the connection as ready, and IBKR warns that earlier calls may be dropped. These ran on 5 November 2026:

**Failure cases, official client 1052.01 against a synthetic socket**

| 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](https://www.interactivebrokers.com/docs/tws-api/sync-doc/introduction), which returns values directly from calls. Its [order timeout](https://www.interactivebrokers.com/docs/tws-api/sync-doc/place-order) 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.

```python
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 matches
```

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

**Order callbacks and IDs, IBKR documentation read 5 November 2026**

| Item | What IBKR says | Consequence |
| --- | --- | --- |
| [orderStatus](https://www.interactivebrokers.com/docs/tws-api/doc/order-management/order-status) | Called on each change; duplicates are common | Compare with the last value before acting |
| [execDetails](https://www.interactivebrokers.com/docs/tws-api/doc/order-management/execution-details) | 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](https://www.interactivebrokers.com/docs/tws-api/doc/order-management/open-orders) | Orders are bound to the client ID that sent them | Only that client can modify them |
| [nextValidId](https://www.interactivebrokers.com/docs/tws-api/doc/next-valid-id) | 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](https://fincodelabs.com/en/blog/trading-bot-development) and [broker API integration](https://fincodelabs.com/en/blog/broker-api-integration-fix-rest).

## 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](https://www.interactivebrokers.com/docs/tws-api/doc/pacing-limitations) 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](https://www.interactivebrokers.com/docs/tws-api/doc/md/market-data-limitations), 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](https://www.interactivebrokers.com/docs/tws-api/doc/download-tws), so disconnects are routine. [connectionClosed](https://www.interactivebrokers.com/docs/tws-api/doc/connectivity/broken-api-socket-connection) 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](https://www.interactivebrokers.com/docs/tws-api/doc/tws-settings/intelligent-order-resubmission), 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](https://www.interactivebrokers.com/docs/tws-api/doc/notes-limitations/tws-api-limitations), 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](https://www.interactivebrokers.com/docs/tws-api/changelog) 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](https://fincodelabs.com/en/blog/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](https://fincodelabs.com/en/services/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)](https://www.interactivebrokers.com/docs/tws-api/doc/download-the-tws-api)
- [IBKR: API Software download page (Stable 1050.02, Latest 1052.01)](https://download2.interactivebrokers.com/installers/tws-api/index.html)
- [IBKR TWS API: Requirements (minimum language versions)](https://www.interactivebrokers.com/docs/tws-api/doc/notes-limitations/requirements)
- [IBKR TWS API: Update The Python Interpreter](https://www.interactivebrokers.com/docs/tws-api/doc/unique-configurations/updating-the-python-interpreter)
- [IBKR TWS API: Placing Orders quick start](https://www.interactivebrokers.com/docs/tws-api/doc/quick-start/placing-orders)
- [IBKR Campus: Installing and Configuring TWS for the API (Read-Only default, ports)](https://www.interactivebrokers.com/campus/trading-lessons/installing-configuring-tws-for-the-api/)
- [IBKR TWS API: Next Valid ID](https://www.interactivebrokers.com/docs/tws-api/doc/next-valid-id)
- [IBKR TWS API: Order Status](https://www.interactivebrokers.com/docs/tws-api/doc/order-management/order-status)
- [IBKR TWS API: Execution Details](https://www.interactivebrokers.com/docs/tws-api/doc/order-management/execution-details)
- [IBKR TWS API: Pacing Limitations](https://www.interactivebrokers.com/docs/tws-api/doc/pacing-limitations)
- [IBKR TWS API: Intelligent Order Resubmission](https://www.interactivebrokers.com/docs/tws-api/doc/tws-settings/intelligent-order-resubmission)
- [IBKR TWS API: Error Codes](https://www.interactivebrokers.com/docs/tws-api/doc/error/codes)
- [IBKR TWS API: Changelog (GPL from 10.49, errorTime in 10.33)](https://www.interactivebrokers.com/docs/tws-api/changelog)
- [PyPI: ibapi 9.81.1.post1](https://pypi.org/project/ibapi/)
- [SPK (Capital Markets Board): Notice on unauthorized leveraged transactions, 23 February 2023 (Turkish)](https://spk.gov.tr/duyurular/basin-duyurulari/2023/izinsiz-kaldiracli-islemlere-iliskin-basin-duyurusu)

> 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](https://fincodelabs.com/en/editorial-policy).

---

Published by Fincode (Fincode Yazılım ve Teknoloji A.Ş.), a software company in Istanbul, Turkey: https://fincodelabs.com/en/blog/interactive-brokers-api-python

Cite as: Fincode, "Interactive Brokers API Python: setup, orders, failures", updated November 6, 2026, https://fincodelabs.com/en/blog/interactive-brokers-api-python
