# Options trading API: symbols, chains, OPRA and assignment

URL: https://fincodelabs.com/en/blog/options-trading-api · 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

Options trading API facts read 6 Nov 2026: OCC symbols, chains and greeks, multi-leg orders, approval levels, assignment events, OPRA fees, tested parser.

**Short answer:** An **options trading API** is a broker interface that adds what a stock API lacks: OCC contract symbols, option chains with greeks, multi-leg orders, an account approval level, and exercise, assignment and expiry events. As read on 6 November 2026, Alpaca's greeks are calculated with Black-Scholes, while Tradier's come from ORATS and update hourly. Real-time US options data is licensed through OPRA, whose fee schedule counts automated trading as non-display use.

Written for developers adding US listed options to a stock integration. Facts come from official broker, exchange and regulator documents read on 6 November 2026; no account was available, so nothing was sent to a broker.

Options carry high risk, and a short option can lose far more than its premium. No broker is recommended or ranked, and there are no referral links. Rules depend on your country, and account types and API access vary. For readers in Turkey: the Capital Markets Board (SPK, also CMB) [said on 23 February 2023](https://spk.gov.tr/duyurular/basin-duyurulari/2023/izinsiz-kaldiracli-islemlere-iliskin-basin-duyurusu) that investors should carry out leveraged transactions only through institutions it has authorized ([Law No. 6362](https://www.mevzuat.gov.tr/mevzuatmetin/1.5.6362.pdf) Articles 99(4) and 109(2)). Article 99(4) lets the Board order online content removed or blocked when such transactions, or derivatives made subject to the same rules, are carried out from abroad for Turkish residents; the notice speaks of leveraged forex, not listed options, so ask a Turkish lawyer how it applies to you.

## Options trading API: what it adds to a stock integration

An options integration needs seven pieces a stock integration lacks.

**Options-specific pieces**

| Piece | Why stock code breaks |
| --- | --- |
| Contract symbol | 21 characters, significant spaces, root not always the ticker |
| Chain query | Many contracts per underlying: filters and paging |
| Greeks and implied volatility | Model outputs; model and age vary by broker |
| Multi-leg orders | Several contracts, one net price |
| Approval level | Checked on every order |
| Exercise, assignment, expiry | Positions change without an order |
| Data licence | OPRA terms and fees |

Contract size is the quiet trap: Alpaca's records carry a `multiplier`, 100 for standard contracts, and non-standard contracts can have several deliverables. Never assume 100 shares.

## OCC option symbol format, and how brokers write it

The OCC symbol is 21 characters: a root of up to six characters padded with spaces, the expiration as yymmdd, C or P, and the strike times 1,000 in eight digits.

tastytrade's symbology page gives `AAPL 220617P00150000` as the AAPL 17 June 2022 150 put and says the padding spaces are significant; such symbols must be URL-encoded in paths and query strings but sent raw in JSON. Alpaca's and Tradier's examples drop the padding, as in `AAPL240119C00100000`.

**One contract, three spellings**

| Form | Example | Where the form appears |
| --- | --- | --- |
| Padded, 21 characters | `AAPL 220617P00150000` | tastytrade docs |
| Compact | `AAPL220617P00150000` | Alpaca and Tradier examples |
| Padded, URL-encoded | `AAPL%20%20220617P00150000` | Paths and query strings; computed with Python's `urllib.parse.quote` |

The root is not always the ticker. Cboe's circular on the Options Symbology Initiative, mandatory from 12 February 2010, allowed roots of up to five characters and gave corporate action symbols a number, such as IBM1; tastytrade's page allows six, so size parsers for six. Alpaca's chain reference gives `AAPL1` as a `root_symbol` example: two contracts can share underlying, expiry, type and strike yet differ in deliverables, which the contracts endpoint lists with `show_deliverables=true`.

## Options chain API: what the chain endpoints return

A chain call returns many contracts for one underlying, and each broker limits it differently, so plan for filters, paging and empty answers.

- **Alpaca snapshots.** `GET /v1beta1/options/snapshots/{underlying_symbol}` returns each contract's latest trade and quote, `greeks` and `impliedVolatility`. `limit` defaults to 100 and caps at 1,000 data points across all symbols; follow `next_page_token`.
- **Alpaca contracts.** `GET /v2/options/contracts` returns 100 rows a page by default (10,000 at most) and, without date filters, only active contracts expiring before the coming weekend, so one default call silently misses later expirations.
- **Tradier.** `GET /v1/markets/options/chains` requires `symbol` and `expiration`; an expiration the symbol does not list returns an empty response with no error, so fetch valid dates first. Greeks need `greeks=true`.

Treat an empty chain as an error (more in the [Tradier API guide](https://fincodelabs.com/en/blog/tradier-api)).

## Option greeks API: who computes the numbers, and when

Greeks are model outputs rather than market data, so store their source and timestamp with every value.

**Greeks as documented, read 6 November 2026**

| Item | Alpaca | Tradier |
| --- | --- | --- |
| Model or source | Black-Scholes, per the chain reference | ORATS, per its market data page |
| Update timing | Not stated on the pages read | Hourly from about 10:15; no time zone given |
| Sandbox | Not stated | Not available |

Black-Scholes ignores early exercise, while Alpaca's contracts carry a `style` of american or european, so its values for American-style contracts can differ from models that price it. Do not mix sources, and never read a missing value as zero.

## Options data API: OPRA licensing and fees

Real-time US listed options quotes come from OPRA, the Options Price Reporting Authority; its agreements and fees apply whether you or your vendor pays.

Alpaca offers `opra`, the consolidated best bid and offer, to subscribers, and a free `indicative` feed, a derivative of OPRA whose quotes are not actual OPRA quotes and whose trades are delayed 15 minutes. Its chain reference gives two different defaults for `feed`, so pass it explicitly.

**OPRA fee schedule (PDF with no revision date), downloaded 6 November 2026**

| Item | Monthly fee |
| --- | --- |
| Professional subscriber with a direct OPRA agreement, per display device (since 1 January 2018) | $31.50 |
| Nonprofessional subscriber, paid by the vendor | $1.25 each up to 75,000, falling in steps to $0.60 above 500,000 |
| Non-display use, Category 1 (own behalf) or 2 (for clients) | $2,000 per enterprise for each category |

Its non-display use covers automated or algorithmic trading engines run by OPRA vendors or professional subscribers with a direct agreement. The Category 1 fee does not apply in a calendar month when the recipient is one natural person with a single user ID, not a broker-dealer, averaging at most 390 listed-option orders a day (trading or calendar days is not stated). Data counts as delayed after at least 15 minutes. Licensing in general is in the [stock market data API guide](https://fincodelabs.com/en/blog/stock-market-data-api).

> **Hypothetical example.** A firm with a direct professional agreement, three display screens and one trading engine for its own account pays 3 x $31.50 = $94.50 plus $2,000 for Category 1 non-display use: $2,094.50 a month, plus any access, vendor or connectivity fees.

## Multi-leg option orders and options approval levels

A spread goes in as one order with a net price and a list of legs, checked against the account's approval level.

**Multi-leg orders as documented, read 6 November 2026**

| Broker | How legs are sent | Rules and checks stated |
| --- | --- | --- |
| Alpaca | `order_class` `mleg`; legs with `symbol`, `ratio_qty`, `side`, `position_intent` | Ratios in simplest form (2:4 becomes 1:2); stop orders single-leg only; no stock legs; all legs covered within the order |
| Tradier | `class` `multileg`, or `combo` with a stock leg; indexed `option_symbol[n]`, `side[n]`, `quantity[n]` | `preview=true` validates without sending |
| tastytrade | Leg actions such as `Sell to Open`; `price-effect` `Debit` or `Credit` | Up to 4 legs, each a distinct symbol; `POST /accounts/{account_number}/orders/dry-run` |

Alpaca rejects two short calls in one order even though sending them separately is allowed; neither Alpaca nor Tradier states a leg maximum on the pages read.

Alpaca's options trading page, updated 16 September 2026, lists four approval levels:

**Alpaca options levels, read 6 November 2026**

| Level | Allows |
| ---: | --- |
| 0 | Options trading disabled |
| 1 | Selling covered calls and cash-secured puts |
| 2 | Level 1, plus buying calls and puts |
| 3 | Levels 1 and 2, plus buying call and put spreads |

Alpaca's paper accounts have options enabled by default, so paper tests prove nothing about the live level. In the US, FINRA Rule 2360(b)(16) requires written approval of the account, after due diligence on facts such as objectives, income, net worth and experience, before option orders are accepted. Read the account's approved and trading levels at start-up and handle rejections (see the [Alpaca API Python guide](https://fincodelabs.com/en/blog/alpaca-api-python)).

## Exercise, assignment and expiry events

Positions change after the close without an order from you, so reconcile from account activities, not just fills.

- **Automatic exercise.** Alpaca exercises contracts in the money by at least $0.01 at expiry unless told otherwise; do-not-exercise requests go through support, with no API described.
- **Forced sale.** Without buying power to exercise, Alpaca sells the position within 1 hour before expiry; no exact time is given.
- **Exercise by API.** `POST /v2/positions/{symbol_or_contract_id}/exercise` exercises the whole holding; requests between the close and midnight are rejected.
- **Assignment.** Not sent over the WebSocket: poll activities for `OPASN`, `OPEXP` and `OPXRC` (assignment, expiration, exercise; activities page updated 16 October 2026). In paper trading they appear only the following day.

Reconcile after the close and before the open, and expect stock from an assignment; a short American-style leg can be assigned early, leaving an option plus stock. See [position reconciliation](https://fincodelabs.com/en/blog/position-reconciliation) and [paper trading API](https://fincodelabs.com/en/blog/paper-trading-api).

## Options API in Python: a tested OCC symbol parser

Validate every symbol where it enters your system, and look a contract up by its terms only when exactly one row matches.

This ran offline with Python 3.11.3 on 6 November 2026, standard library only, on synthetic rows for a made-up XYZ.

```python
import re
from datetime import date, datetime
from decimal import Context, Decimal, Inexact

OCC = re.compile(r"([A-Z0-9]{1,6}) *(\d{6})([CP])(\d{8})", re.ASCII)

def to_occ(root, expiry, right, strike, padded=True):
    if isinstance(strike, float):
        raise TypeError("strike must be str or Decimal, not float")
    try:  # no silent rounding
        milli = Decimal(strike).scaleb(3, Context(traps=[Inexact]))
    except ArithmeticError:
        raise ValueError(f"bad strike {repr(strike)}") from None
    if not milli.is_finite() or not 0 < milli < 10**8 or milli % 1:
        raise ValueError(f"strike not encodable: {repr(strike)}")
    if not re.fullmatch(r"[A-Z0-9]{1,6}", root) or right not in ("C", "P"):
        raise ValueError(f"bad root or right: {repr(root)} {repr(right)}")
    head = root.ljust(6) if padded else root
    return f"{head}{expiry:%y%m%d}{right}{int(milli):08d}"

def parse_occ(symbol):  # padded or compact form
    m = OCC.fullmatch(symbol)
    if not m or len(symbol) > 21 or (" " in symbol and len(symbol) < 21):
        raise ValueError(f"not an OCC symbol: {repr(symbol)}")
    root, ymd, right, digits = m.groups()
    expiry = datetime.strptime(ymd, "%y%m%d").date()
    strike = Decimal(digits) / 1000
    to_occ(root, expiry, right, strike)  # rejects a zero strike
    return root, expiry, right, strike

def find_one(rows, expiry, kind, strike, root=None):  # never guess
    hits = [r for r in rows if r.get("expiration_date") == expiry
            and r.get("type") == kind and root in (None, r.get("root_symbol"))
            and Decimal(str(r.get("strike_price"))) == Decimal(strike)]
    if len(hits) == 1:
        parse_occ(hits[0].get("symbol") or "")  # rejects an empty symbol
        return hits[0]["symbol"]
    raise LookupError(f"{len(hits)} matches: {[r.get('symbol') for r in hits]}")

for s in ["SPXW  220520C04025000", "AAPL240119C00100000"]:
    print(*parse_occ(s))
d = date(2026, 12, 18)
tests = [lambda: parse_occ("AAPL 240119C00100000"),
         lambda: parse_occ("AAPL  261131C00100000"),
         lambda: to_occ("XYZ", d, "C", 190.1),
         lambda: to_occ("XYZ", d, "C", "1e400"),
         lambda: to_occ("XYZ", d, "C", "1e-400"),
         lambda: to_occ("XYZ", d, "C", "50.0000000000000000000000000001")]
for test in tests:
    try:
        test()
    except (ValueError, TypeError) as e:
        print(type(e).__name__, "-", e)

rows = [{"symbol": s, "root_symbol": s[:-15], "expiration_date": "2026-12-18",
         "type": "call", "strike_price": "50"}  # synthetic, Alpaca field names
        for s in ("XYZ261218C00050000", "XYZ1261218C00050000")]
for args in [("call", "50"), ("call", "50", "XYZ"), ("put", "50")]:
    try:
        print(find_one(rows, "2026-12-18", *args))
    except LookupError as e:
        print("LookupError -", e)
```

Output:

```
SPXW 2022-05-20 C 4025
AAPL 2024-01-19 C 100
ValueError - not an OCC symbol: 'AAPL 240119C00100000'
ValueError - day is out of range for month
TypeError - strike must be str or Decimal, not float
ValueError - strike not encodable: '1e400'
ValueError - strike not encodable: '1e-400'
ValueError - bad strike '50.0000000000000000000000000001'
LookupError - 2 matches: ['XYZ261218C00050000', 'XYZ1261218C00050000']
XYZ261218C00050000
LookupError - 0 matches: []
```

The checks catch a single space that shifts every field, 31 November, a float strike (190.1 is stored as 190.0999...), `"1e400"` and `"1e-400"`, which overflow eight digits or vanish at three decimals, and a 30-digit strike that plain Decimal multiplication would round to 50. The lookup found XYZ and the adjusted XYZ1 and refused to pick. For integration work in general, see Fincode's [broker API integration page](https://fincodelabs.com/en/services/broker-api-integration); naming Alpaca, Tradier or tastytrade here does not mean Fincode integrates them or has built on them.

## Frequently asked questions

### Which brokers offer an options trading API?

Alpaca, Tradier and tastytrade each document option symbols and multi-leg orders, as read on 6 November 2026; this is neither a ranking nor a complete list.

### Is there a free options data API?

Alpaca's indicative feed is free, but its quotes are not actual OPRA quotes and its trades are delayed 15 minutes; real-time OPRA data is licensed.

### How do I parse an OCC option symbol in Python?

Read the last 15 characters as yymmdd, C or P, and an eight-digit strike divided by 1,000; the rest, minus padding spaces, is the root. Keep the strike as a Decimal.

### Can I trade options through an API without options approval?

No. Orders are checked against the account's options level, and in the US, FINRA Rule 2360 requires written account approval before option orders are accepted.

## Sources

- [tastytrade Developer Docs: Instruments and Symbology (OCC option format)](https://developer.tastytrade.com/docs/concepts/instruments-and-symbology/)
- [tastytrade Developer Docs: Multi-leg spreads (4 legs, price-effect, dry run)](https://developer.tastytrade.com/docs/guides/multi-leg-spreads/)
- [Cboe Informational Circular IC10-48: OSI, Options Symbology Initiative (9 February 2010)](https://cdn.cboe.com/resources/regulation/circulars/general/IC-CBOE-2010-048.pdf)
- [Alpaca Docs: Options Trading (levels, exercise, assignment; updated 16 September 2026)](https://docs.alpaca.markets/docs/options-trading)
- [Alpaca Docs: Options Level 3 Trading (multi-leg orders)](https://docs.alpaca.markets/docs/options-level-3-trading)
- [Alpaca API reference: Option chain snapshots (filters, greeks, feed)](https://docs.alpaca.markets/reference/optionchain)
- [Alpaca API reference: Get option contracts (multiplier, deliverables, default dates)](https://docs.alpaca.markets/reference/get-options-contracts)
- [Alpaca Docs: Historical option data (indicative and OPRA feeds)](https://docs.alpaca.markets/docs/historical-option-data)
- [Alpaca Docs: Account activities (OPASN, OPEXP, OPXRC; updated 16 October 2026)](https://docs.alpaca.markets/docs/account-activities)
- [Tradier Docs: Get Options Chains (required expiration, greeks parameter)](https://docs.tradier.com/reference/brokerage-api-markets-get-options-chains)
- [Tradier Docs: Market Data (realtime and delayed, greeks from ORATS hourly)](https://docs.tradier.com/market-data/get-option-chains)
- [Tradier Docs: Place Order (option, multileg and combo classes, preview)](https://docs.tradier.com/trading/place-order)
- [OPRA: Fee Schedule (professional, nonprofessional and non-display fees)](https://cdn.opraplan.com/documents/OPRA_Fee_Schedule.pdf)
- [FINRA Rule 2360: Options (account approval and disclosure document)](https://www.finra.org/rules-guidance/rulebooks/finra-rules/2360)
- [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/options-trading-api

Cite as: Fincode, "Options trading API: symbols, chains, OPRA and assignment", updated November 6, 2026, https://fincodelabs.com/en/blog/options-trading-api
