What IBKR Error Codes Taught Me About Bot Reliability

When I started wiring Interactive Brokers into this bot back in late February, I budgeted my anxiety for the wrong thing. I assumed order placement would be the treacherous part — contracts, routing, validation. In practice, the order path was the easy part. The code I've written since then tells a different story: the real work of an IBKR integration is surviving the API when it's unhappy, and it has a remarkable number of ways to be unhappy.

Here's the tour, in roughly the order each failure mode found me.

Error 10197: the session that fights itself

IBKR market-data sessions don't like company. If two things request data under the same session in the wrong way, you get Error 10197 — a session conflict — and requests start failing in ways that look, at first, like everything and nothing.

This one error drove more architecture than any feature ever did. The commit history from May reads like an escalation ladder: first instrument the error with occurrence counters and workflow tracking so I could see it at all; then add pre-flight conflict guards before chain requests; then mid-cycle checks inside long-running tools; then a TTL-based recovery path so a conflicted session heals itself instead of requiring me to notice. Eventually the honest fix was admitting that two independent consumers of one session was the bug, not the errors it produced — so I consolidated the competing consumers instead of letting them keep contending.

There's a lesson in there: when an external API keeps failing the same way, the failure is usually telling you something about your process topology, not about the API.

Pacing violations: centralizing broker calls

IBKR enforces request pacing, and in my integration, pacing violations tended to show up as degraded, laggy downstream behavior rather than a clean rejection. My early fixes were whack-a-mole: retry here, timeout there. The durable lesson was to centralize: route every broker call through one shared path, so that timeouts, logging, and rate discipline live in a single place instead of being reinvented at every call site. Migrating everything onto that path was tedious. It also paid for itself more than any other refactor in the integration, because every subsequent defense had one place to live.

One subtlety that bit me: a slow response because you asked too fast is not the same as a broken endpoint. Failure handling has to keep transient pacing pressure separate from genuine endpoint ill-health, or the system ends up punishing itself for its own impatience.

Errors 1100/1102: the connection that lies about being alive

The socket can look perfectly healthy while the far side has silently died. IBKR signals connectivity loss and restoration with error codes 1100 and 1102, and if you're not listening for them, your bot happily operates on a dead line.

The handling for this went through several versions, and a few lessons survived all of them. Treat connectivity loss as a reason to stop and be loud, not a condition to guess through. Dedupe the alerting so a flappy connection doesn't page me forty times. And — this was the non-obvious part — after a reconnect, verify the API is actually ready before trusting it again. "Connected" is a socket state; "ready" is a behavior, and only one of them matters.

The broader lesson: not all connectivity failures mean the same thing. A brief burst of flapping and a sustained loss are different events that deserve different responses, so classify them instead of lumping everything under "disconnected."

Error 201: the precaution you didn't know you agreed to

IBKR applies configurable "order precautions" server-side, and some of them will reject perfectly valid requests. The generic lesson here is that a broker account carries settings you may never have consciously chosen, and their rejections can masquerade as generic failures. Two things help: surface Error 201 as its own distinct reason so it stops hiding among unrelated errors, and review the account-level settings deliberately instead of living with whatever defaults the account came with.

Error 200: the contract that doesn't exist

Some contract definitions turn out to be invalid for the requested instrument context, and asking for one gets you Error 200 and a failed request. The fix was to validate requested contract fields against IBKR's available contract metadata before submitting the request. Boring, mechanical, and it eliminated a whole category of noise.

The cheap defense: whatIf

The most useful discovery of the integration was IBKR's whatIf flag — a full validation pass, including margin checks, that never executes anything. The general lesson: when a broker offers a server-side dry run, it converts a class of would-be runtime rejections into pre-flight information, at the cost of one extra round-trip. That's cheap insurance for any integration.

What I'd tell past me

Four months in, the pattern is clear. Every defense — centralized broker calls, readiness verification, failure classification, conflict guards, dry-runs — exists because an error code taught it to me. None of them were in the original design, because the original design assumed the API would mostly work. It mostly does. But an autonomous bot has no human watching the terminal to notice the rare times it doesn't, so "mostly works" has to be engineered into "fails loudly, recovers automatically, and never proceeds on an unverified connection state."

Looking back over the work, the order path settled down within the first weeks of the integration; the connection-trust work was still accreting defenses four months later. That ratio surprised me more than any single error code did.

Disclaimer: This journal documents a personal software-engineering project. The system described trades a paper (simulated) account. Nothing here is investment advice, a recommendation, or a signal, and no market data or trading performance is provided. Content is about building software.