About this guide: I'm Lawrence, the writer behind supa.is. Between February and May 2026 I've published 150+ articles on supa.is across crypto and brokerage tooling — including 30+ Interactive Brokers-specific guides (recent examples: Fix IBKR ib_async Connection Drops, Fix IBKR reqTickersAsync Stale Ticker Bug, IBKR Python API Error Handling: Codes & Fixes). The most-repeated reader question across that IBKR API archive is exactly how to handle theOptionChainunderlyingConIdstring bug, which is why I'm publishing this standardized guide instead of answering one-off.
If you use the Interactive Brokers (IBKR) API to fetch option chains in Python, you've likely encountered a frustrating type mismatch. When querying the OptionChain, the underlyingConId field returns as a string instead of an integer. While this seems like a minor data type issue, it breaks downstream logic—especially if you're passing the underlyingConId directly into other IBKR API calls that strictly require an integer type.
This bug has been documented in the ib_async GitHub repository and affects both the official TWS API and third-party wrappers like ib_async. In this guide, we'll break down exactly why this happens, how it breaks your code, and the exact workaround you need to implement to keep your option chain parsing running smoothly.
Why the underlyingConId String Bug Happens
The Interactive Brokers API is built on a legacy protocol that predates modern JSON standards. Data is transmitted over a binary socket connection, and fields are parsed based on a strict protocol definition.
When the IBKR TWS or Gateway server sends the OptionChain response, the underlyingConId is encoded as a string in the underlying protocol buffer. The official IBKR API documentation states that underlyingConId should be an integer (since contract IDs are inherently numeric). However, the raw protocol parsing in the Java client—and by extension, the Python wrappers—sometimes fails to cast this field back to an integer, leaving it as a string.
This issue was highlighted in the ib_async community, where developers reported that fetching option chains resulted in the underlyingConId being a string (e.g., "6318" instead of 6318).
How It Breaks Your Code
If you're writing robust Python code, this bug causes a hard crash. Here is a typical scenario:
# Pseudo-code of what goes wrong
option_chain = ib.reqOptionChain(symbol, exchange, underlyingSecType, underlyingConId)
underlying_id = option_chain.underlyingConId
# Crash happens here because the API expects an int, but gets a string
ib.reqMktData(underlying_id, ...)
If you pass the string "6318" into a function expecting an int, Python will throw a TypeError. Even worse, if you are using a type-hinted library or a strict data validation framework, the data pipeline will reject the payload entirely.
The Workaround: Casting underlyingConId to Int
The fix is straightforward: explicitly cast the underlyingConId to an integer before using it in subsequent API calls.
Step 1: Fetch the Option Chain
First, you request the option chain as you normally would. Let's use ib_async as the example, since it's the most popular Python wrapper for IBKR.
from ib_async import IB, Stock
ib = IB()
ib.connect('127.0.0.1', 7497, clientId=1)
# Define the underlying contract
underlying = Stock('AAPL', 'SMART', 'USD')
# Fetch the option chain
option_chain = ib.reqOptionChain(underlying)
Step 2: Safely Cast the underlyingConId
To prevent crashes in edge cases (like a missing or null value), wrap the cast in a try-except block:
# Extract the underlyingConId
raw_underlying_conid = option_chain.underlyingConId
# Safely cast to integer
try:
underlying_conid = int(raw_underlying_conid)
except (ValueError, TypeError):
underlying_conid = None
print(f"Warning: Could not cast underlyingConId '{raw_underlying_conid}' to int.")
Like what you're reading? Try it yourself — this link supports ChartedTrader at no cost to you.
Sign up for Interactive Brokers →
By doing this, you ensure that underlying_conid is now a native Python integer, ready to be used in other IBKR API calls.
Step 3: Use the Integer in Subsequent Calls
Now you can pass the integer underlying_conid into other API methods without triggering a TypeError.
if underlying_conid:
# Fetch market data using the correctly typed ConId
contract = Stock(conId=underlying_conid, exchange='SMART', currency='USD')
bars = ib.reqHistoricalData(contract, ...)
Alternative: Using ib_async's Built-in Type Handling
If you are using the ib_async library, it attempts to handle type casting under the hood, but the OptionChain object is a direct mapping of the raw protocol buffer. Therefore, the bug persists in the raw object.
If you are building a larger application, create a wrapper function to handle this type coercion automatically. This keeps your main trading logic clean.
def get_valid_underlying_conid(option_chain):
"""Extracts and casts the underlyingConId from an OptionChain object."""
try:
return int(option_chain.underlyingConId)
except (ValueError, TypeError, AttributeError):
return None
# Usage
valid_conid = get_valid_underlying_conid(option_chain)
Why IBKR Hasn't Fixed This Yet
The Interactive Brokers API is notoriously slow to update. The core protocol is maintained by IBKR's internal engineering team, and changes to the data types transmitted over the wire require extensive testing across all their client platforms (TWS, Mobile, API).
Because the string-to-integer mismatch doesn't break the TWS GUI (which handles the data internally), it's treated as a low-priority bug. The community, particularly ib_async maintainers, has flagged it multiple times (as seen in GitHub issue #221), but until IBKR officially patches the protocol, developers have to handle the type casting manually.
Best Practices for IBKR API Option Chain Parsing
When working with IBKR's API, defensive programming is your best friend. Keep these practices in mind:
- Never assume data types: IBKR's legacy protocol frequently returns strings where numbers are expected.
- Wrap casts in try-except blocks: Prevent a single bad data point from crashing your entire trading bot.
- Keep
ib_asyncupdated: While theunderlyingConIdbug is protocol-level,ib_asyncoccasionally releases patches that improve type handling for other fields. - Cache your data: The IBKR API has strict rate limits. If you are fetching option chains repeatedly, cache the data locally and only refresh it when necessary.
Common Errors When Fixing the Bug
While implementing the fix, you might run into a few common errors:
* ValueError: invalid literal for int(): This happens if the underlyingConId is an empty string or contains non-numeric characters. The try-except block mentioned above will catch this.
AttributeError: 'NoneType' object has no attribute 'underlyingConId': This means your option_chain object is null. Ensure your ib_async connection is active and the contract you are querying actually has options available.
* TypeError: 'float' object cannot be interpreted as an integer: In rare cases, IBKR might return a float. If you encounter this, use int(float(raw_underlying_conid)) as a fallback.
Conclusion
The IBKR API's OptionChain returning underlyingConId as a string is a known, persistent bug. While frustrating, it is easily mitigated by explicitly casting the value to an integer before passing it to other API methods. Until IBKR updates its core protocol, this workaround will be the standard solution for the community.
FAQ
Is this bug exclusive to ib_async?
No. The bug originates from the IBKR TWS/Gateway protocol itself. While ib_async is the most commonly affected library because it exposes the raw protocol data directly, the native IBKR Python API and other wrappers experience the same string-to-integer mismatch.
Will IBKR fix this bug in the future?
It is possible, but unlikely in the short term. IBKR's API is legacy-based, and changes to the protocol require extensive testing. Since the bug doesn't break the TWS GUI, it is not a high priority for IBKR's engineering team.Can I use float() instead of int() to cast the underlyingConId?
Technically yes, but it is not recommended. Contract IDs are inherently whole numbers. Using float() might introduce floating-point precision errors downstream, especially if you are comparing IDs or using them in database queries. Always cast to int().
Does this bug affect other fields in the OptionChain?
Yes, other fields in the OptionChain and related API responses can sometimes return as strings when integers are expected. It is a good practice to validate and cast data types for all numeric fields when working with IBKR's API.
How do I know if my ib_async is up to date?
You can check your version by running import ib_async; print(ib_async.__version__) in your Python environment. To update it, use pip install --upgrade ib_async.
Risk Warning
Risk Warning: Trading options and using algorithmic trading via the IBKR API involves substantial risk of loss. API bugs, connection drops, and data type mismatches can lead to unintended orders or missed trades. Never invest more than you can afford to lose. This is not financial advice.