# ib_insync vs ib_async: what changes for a live bot

URL: https://fincodelabs.com/en/blog/ib-async-vs-ib-insync · 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

ib_insync vs ib_async: ib_insync is frozen at 0.9.86. A reproducible API diff, behavior tests on a mock socket, error 200 handling and a migration checklist.

**Short answer:** **ib_insync vs ib_async**: ib_async is the maintained continuation and ib_insync is frozen at 0.9.86 from July 2023, so new work and live bots should plan to move. A static comparison run on 5 November 2026 found no public name removed and only IB.connect, IB.connectAsync and IB.qualifyContractsAsync changed in signature, but 69 of 420 function bodies differ, and tests on a synthetic socket showed different results for unknown contracts, order errors and state after disconnect. Both are third-party libraries, not affiliated with IBKR.

Most migration notes stop at the import rename. That is enough to make a bot start, and not enough to make it behave the same. This guide publishes the comparison so you can repeat it, then shows where behavior differs, tested on 127.0.0.1 against a synthetic server because no IBKR account was available. A person still has to run the result on a paper account.

These are third-party libraries under the BSD 2-Clause license (PyPI's classifier for ib_async says Other/Proprietary, but its LICENSE file and license field say BSD), and this guide contains no strategy. No broker is recommended and there are no referral links; rules depend on your country and 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)). Automated trading can lose money. First-time setup is in [Interactive Brokers API Python](https://fincodelabs.com/en/blog/interactive-brokers-api-python).

## ib_insync vs ib_async vs ibapi: status on 5 November 2026

ib_insync is archived, ib_async is active but heading for a large release, and ibapi is the only one IBKR supports directly.

**The three Python clients, from PyPI, GitHub and the wheels, 5 November 2026**

| Item | ib_insync | ib_async | ibapi (official) |
| --- | --- | --- | --- |
| Latest release | 0.9.86, 2 July 2023 | 2.1.0, 8 December 2025 | 1052.01, 30 October 2026 |
| Python | 3.6 or newer; classifiers stop at 3.11 | 3.10 or newer; classifiers reach 3.14 | 3.11 or newer |
| Repository | Archived 14 March 2024 | Active; commits on a development branch to 4 November 2026 | Installer download; private beta repository |
| Client version offered | v157..176 | v157..178 | v100..226 |
| Wire format | Text | Text | Protobuf, message by message, from server version 201 |
| Needs ibapi | No | No | It is ibapi |
| Style | Sync and asyncio | Sync and asyncio | Callbacks, or TWSSyncWrapper |
| License | BSD 2-Clause | BSD 2-Clause | GPL-3.0-or-later |

ib_insync's author, Ewald de Wit, died on 11 March 2024, as a family notice in the [project's README](https://github.com/erdewit/ib_insync) records. ib_async 1.0.0 followed a week later under new maintainers and [described itself](https://ib-api-reloaded.github.io/ib_async/changelog.html) as functionally equal to 0.9.86.

[IBKR's own note](https://www.interactivebrokers.com/docs/tws-api/doc/third-party-api-platforms) says ib_insync uses a legacy API release and is no longer updated, and tells those who want that structure on supported TWS releases to move to ib_async, which it calls more recent though still legacy. It says this is not an endorsement, offers no coding help for these packages, can review your API logs, and advises its direct API whenever possible. Whether a current TWS keeps accepting clients at version 176 or 178 could not be tested here, though the report in issue 243 suggests ib_async 2.1.0 still connected on 4 November 2026.

## The API diff: no names removed, 69 function bodies changed

Every public name in ib_insync 0.9.86 still exists in ib_async 2.1.0, so name-level breakage is rare, but that says little about behavior.

The script reads both wheels as text and parses them with ast, without importing or running them. It compares public module functions, classes, methods and their argument names; for bodies it strips docstrings and normalizes the package name. A changed body is not always a behavior change: 4 of the 69 differ only in annotations or in the container used for a membership test. The wheel hashes matched PyPI's published values.

```python
import ast, sys, zipfile

FUNC = (ast.FunctionDef, ast.AsyncFunctionDef)

def scan(wheel, pkg):
    names, bodies = {}, {}
    z = zipfile.ZipFile(wheel)
    for n in z.namelist():
        if not (n.startswith(pkg + "/") and n.endswith(".py")):
            continue
        mod = n[len(pkg) + 1:-3]
        for node in ast.parse(z.read(n).decode()).body:
            if getattr(node, "name", "_").startswith("_"):
                continue
            funcs = [(node.name, node)] if isinstance(node, FUNC) else []
            if isinstance(node, ast.ClassDef):
                names[f"{mod}.{node.name}"] = None
                funcs = [(f"{node.name}.{m.name}", m) for m in node.body
                         if isinstance(m, FUNC) and not m.name.startswith("_")]
            for name, fn in funcs:
                names[f"{mod}.{name}"] = [a.arg for a in fn.args.args + fn.args.kwonlyargs]
                body = fn.body[1:] if ast.get_docstring(fn) else fn.body
                bodies[f"{mod}.{name}"] = "".join(map(ast.dump, body)).replace(pkg, "PKG")
    return names, bodies

old, old_b = scan(sys.argv[1], "ib_insync")
new, new_b = scan(sys.argv[2], "ib_async")
common = old_b.keys() & new_b.keys()
print("public names", len(old), len(new))
print("removed", len(old.keys() - new.keys()), "added", len(new.keys() - old.keys()))
print("signatures changed", sorted(k for k in old.keys() & new.keys() if not old[k] == new[k]))
print("function bodies", len(common), "changed", sum(not old_b[k] == new_b[k] for k in common))
```

```
$ python api_diff.py ib_insync-0.9.86-py3-none-any.whl ib_async-2.1.0-py3-none-any.whl
public names 527 540
removed 0 added 13
signatures changed ['ib.IB.connect', 'ib.IB.connectAsync', 'ib.IB.qualifyContractsAsync']
function bodies 420 changed 69
```

All additions are optional: connect gains raiseSyncErrors and fetchFields, qualifyContractsAsync gains keyword-only returnAll, and IB() gains defaults, which the script skips as a private name. Added names include Trade.isWaiting, Trade.isWorking, OrderStateNumeric, IBDefaults and StartupFetch.

## Behavior differences on a synthetic socket

The same probe, run against the same local mock, gave different results in the places a live bot is most likely to depend on.

**Same script on each library, 5 November 2026, Python 3.11.3, synthetic server only**

| Probe | ib_insync 0.9.86 | ib_async 2.1.0 |
| --- | --- | --- |
| qualifyContracts on an unknown contract | [] | [None] |
| [c] = ib.qualifyContracts(...) | ValueError at that line | c is None; AttributeError later in placeOrder |
| Error 202 on a working order | Stays Submitted, active | Cancelled, done |
| Error 321 on a working order | Cancelled, done | ValidationError, active |
| Error 110 on a working order | Stays Submitted, active | ValidationError, active |
| Error 439 on a working order | Cancelled, done | Cancelled, done |
| disconnect() returns | None | A status string |
| ib.trades() after disconnect, with 4 before | 4 | 0, state is cleared |
| Inactive status | Not a done state | A done state |
| Plain limit order on the wire, both at server version 176 | 115 fields | 115 fields, identical |

Three of these need a decision. A smaller result list becoming a list holding None moves the failure away from the cause; the [2.0.0 changelog](https://ib-api-reloaded.github.io/ib_async/changelog.html) marks this as breaking. A new ValidationError status means an order that looks wrong may still be live, and a switch on status with no default branch will miss it. And [issue 243](https://github.com/ib-api-reloaded/ib_async/issues/243), opened on 4 November 2026, reports that a refused modification with error 439 marked a working order Cancelled in 2.1.0 on a real TWS session. That is a user report, and the probe above only confirms that the library treats 439 that way. Read order state back with reqOpenOrders rather than trusting a local Cancelled after an error.

> **Hypothetical example.** A bot counts its resting orders by checking whether status is Submitted or PreSubmitted. After error 110 on a resting order, ib_async reports ValidationError, the count falls to zero and the bot may place another order while the first is still live. Trade.isActive() returned True in that test, so use it instead.

At their own highest versions the libraries send 115 and 114 fields, because ib_async drops faProfile from version 177, so compare wire logs only at the same negotiated version.

## ib_async error 200: what it means and how to handle it

Error 200 comes from IBKR rather than from the library: [its list](https://www.interactivebrokers.com/docs/tws-api/doc/error/codes) uses the code when no security definition matches and when a contract description is ambiguous.

Both libraries logged the same lines in the test, apart from the logger name; only the return value changed:

```
ib_async.wrapper ERROR Error 200, reqId 103: No security definition has been found for the request, contract: Stock(symbol='NOPE', exchange='SMART', currency='USD')
ib_async.ib WARNING Unknown contract: Stock(symbol='NOPE', exchange='SMART', currency='USD')
```

```
contract = Stock("XYZ", "SMART", "USD")          # placeholder symbol
[qualified] = ib.qualifyContracts(contract)
if qualified is None:                            # ib_async: no match, or ambiguous
    raise RuntimeError(f"not qualified: {contract}")
trade = ib.placeOrder(qualified, LimitOrder("BUY", 10, 10.0))
```

To fix the cause, IBKR's list suggests setting the primary exchange when a symbol, currency and exchange are not unique, and the multiplier for futures with several at one expiration. In ib_async, await ib.qualifyContractsAsync(contract, returnAll=True) returns the candidates for an ambiguous request instead of None; the synchronous qualifyContracts has no such argument. An offline test that fed two candidates into the library's own logic returned both with returnAll=True and [None] without it; no real ambiguous reply was involved.

## Migration checklist for a live ib_insync bot

Treat this as a code change with tests, not a rename, and keep a way back.

1. **Pin both.** Keep ib_insync==0.9.86 in a rollback environment and move to ib_async==2.1.0 in a new one on Python 3.10 or newer. The install pulled aeventkit instead of eventkit, and tzdata below 2026.0, which can conflict with other pins. PyPI lists Python 3.14, but [issue 200](https://github.com/ib-api-reloaded/ib_async/issues/200) reports util.startLoop() failing there, marked as fixed for 3.0; the thread gives a nest_asyncio2 workaround, not tested here.
2. **Rename everywhere.** Imports, but also logger names such as ib_insync.wrapper. Pickled objects fail too: a Stock pickled under ib_insync raised ModuleNotFoundError under ib_async.
3. **Search for qualifyContracts.** Handle None at each call, and any code that assumed failures were left out of the list.
4. **Review status logic.** Handle ValidationError and ApiUpdate as active, and Inactive as done. Check any handler keyed on error codes 202, 321, 329 or 105.
5. **Read state before disconnect.** Anything that reads trades or positions after disconnect() now gets nothing.
6. **Decide on startup errors.** With the default raiseSyncErrors=False, a timed-out sync request is only logged. Set it to True if a bot must not start on partial state.
7. **Recheck ports.** The README's connection notes pair 7497 with TWS and 4001 with Gateway; by IBKR's defaults the first is paper and the second is live.
8. **Test on paper, by a person.** Place, modify, reject and cancel an order, restart the gateway, and compare the library's state with TWS. Tests show how software behaves, not future results.

## ib_async 3.0: plan for a second step

A protobuf-based 3.0 is in preparation and it changes more than 2.x did, so pin 2.1.0 now and treat 3.0 as a separate migration.

On 5 November 2026 PyPI's latest was still 2.1.0. The [next-protobuf branch](https://github.com/ib-api-reloaded/ib_async/blob/next-protobuf/docs/changelog.rst) labels 3.0.0 unreleased, and a maintainer wrote on issue 243 the day before that it is releasing soon, with no date. As the branch stands, it needs Python 3.11, depends on protobuf 7.36.2 or newer, and targets server version 226. Its [migration guide](https://github.com/ib-api-reloaded/ib_async/blob/next-protobuf/docs/migration/v3.md) lists three main breaks:

- Financial values become Decimal or None instead of float, so arithmetic and comparisons need checking.
- commission becomes commissionAndFees, with the old name deprecated.
- Records such as AccountValue and Position become frozen dataclasses, so tuple indexing stops working.

The branch also addresses the refused-modification problem above. All of this can change before release, so reread those files when it ships, and repeat the probes.

## ib_async documentation, ib_insync documentation and getting help

Use each project's own site for its docs, and expect IBKR to help only with what its logs show.

[ib_async's documentation](https://ib-api-reloaded.github.io/ib_async/) and [ib_insync's](https://ib-insync.readthedocs.io/) were both online on 5 November 2026; only the former will change. Questions about ib_async belong in its [issue tracker](https://github.com/ib-api-reloaded/ib_async/issues). If you want to stay closer to IBKR, the official client and its TWSSyncWrapper are covered in the [Python guide](https://fincodelabs.com/en/blog/interactive-brokers-api-python), at the cost of rewriting against a different interface and the GPL terms.

Order-state design applies whichever library you use; see [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). A bot stuck on ib_insync can be quoted for review through [trading bot code review](https://fincodelabs.com/en/services/trading-bot-code-review); naming these libraries does not mean Fincode has built on them.

## Frequently asked questions

### Is ib_insync dead?

It no longer receives updates: its repository was archived on 14 March 2024 and its last release is 0.9.86 from July 2023. It may still connect, but this guide could not test it against a current TWS.

### Does ib_async need the ibapi package?

Version 2.1.0 does not. Its README says it implements the protocol itself, and its install pulled only aeventkit, nest_asyncio, tzdata and numpy in the test.

### Is migrating from ib_insync to ib_async just a rename?

No. The rename makes it run, but qualifyContracts, error handling, order statuses and state after disconnect behave differently, as the probes here show.

### Which ib_async version should a live bot use today?

This guide gives no recommendation beyond pinning an exact version. On 5 November 2026 the released version was 2.1.0, and 3.0 was unreleased with larger breaking changes.

### Is ib_async affiliated with Interactive Brokers?

No. IBKR says its note on these packages is neither an endorsement nor a criticism, and that it provides no coding help for them.

## Sources

- [PyPI: ib_async 2.1.0](https://pypi.org/project/ib_async/)
- [PyPI: ib_insync 0.9.86](https://pypi.org/project/ib_insync/)
- [GitHub: ib_async repository and README](https://github.com/ib-api-reloaded/ib_async)
- [GitHub: ib_insync repository (archived, family notice)](https://github.com/erdewit/ib_insync)
- [ib_async documentation: Changelog (2.0.0 breaking changes, 1.0.0 notes)](https://ib-api-reloaded.github.io/ib_async/changelog.html)
- [ib_async next-protobuf branch: v3 migration guide (unreleased)](https://github.com/ib-api-reloaded/ib_async/blob/next-protobuf/docs/migration/v3.md)
- [ib_async issue 243: refused modification marks a working order Cancelled (user report, 4 November 2026)](https://github.com/ib-api-reloaded/ib_async/issues/243)
- [ib_async license (BSD 2-Clause)](https://github.com/ib-api-reloaded/ib_async/blob/main/LICENSE)
- [IBKR TWS API: Third Party API Platforms (ib_insync and ib_async note)](https://www.interactivebrokers.com/docs/tws-api/doc/third-party-api-platforms)
- [IBKR TWS API: Error Codes (200, 202, 321, 110)](https://www.interactivebrokers.com/docs/tws-api/doc/error/codes)
- [IBKR: API Software download page (official client versions)](https://download2.interactivebrokers.com/installers/tws-api/index.html)
- [ib_insync documentation](https://ib-insync.readthedocs.io/)
- [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/ib-async-vs-ib-insync

Cite as: Fincode, "ib_insync vs ib_async: what changes for a live bot", updated November 6, 2026, https://fincodelabs.com/en/blog/ib-async-vs-ib-insync
