Skip to main content

Cliente ligero para API de Schwab: OAuth, REST y Streaming WebSocket

Project description

Schwab SDK (Python)

Lightweight client for the Schwab API: OAuth, REST (Trader/Market Data), and WebSocket Streaming.

  • Focus: thin wrappers, no heavy validation; robust token handling, refresh, and retries.
  • Coverage: Accounts, Orders, Market Data, and Streaming (Level One, Book, Chart, Screener, Account Activity).

Installation

Unofficial PyPI package (distribution name):

pip install schwab_sdk_unofficial

Import in code (module):

from schwab_sdk import Client

⚠️ Note: The package name on PyPI is schwab_sdk_unofficial, but the import remains schwab_sdk for a clean API.

Table of Contents

Requirements

  • Python 3.9+
  • Install dependencies:
pip install requests websocket-client flask

Configuration

Create .env (or export environment variables):

SCHWAB_CLIENT_ID=your_client_id
SCHWAB_CLIENT_SECRET=your_client_secret
SCHWAB_REDIRECT_URI=https://127.0.0.1:8080/callback

Tokens are saved in schwab_tokens.json and rotate automatically (access ~29 min; re-login notice when refresh expires ~7 d).

Quick Start

from schwab_sdk import Client
import os

client = Client(
    os.environ['SCHWAB_CLIENT_ID'],
    os.environ['SCHWAB_CLIENT_SECRET'],
    os.environ.get('SCHWAB_REDIRECT_URI','https://127.0.0.1:8080/callback')
)

# First use: OAuth login (opens the browser)
client.login()

# REST
quotes = client.market.get_quotes(["AAPL","MSFT"])  # Market Data
accounts = client.account.get_accounts()               # Accounts

# Streaming (Level One equities)
ws = client.streaming
ws.on_data(lambda f: print("DATA", f))
ws.connect(); ws.login()
ws.equities_subscribe(["AAPL"])

Authentication (OAuth)

  • client.login(timeout=300, auto_open_browser=True)
  • Handy: client.has_valid_token(), client.refresh_token_now(), client.logout()
  • Internals: adhoc HTTPS callback server (dev), code-for-token exchange, auto-refresh and notice when refresh expires.

Request & Error Handling (REST)

All REST calls use Client._request() with:

  • Automatic Authorization headers
  • Refresh retry on 401 (once) and immediate resend
  • Retries with backoff for 429/5xx (exponential with factor 0.5)

Accounts (accounts.py)

get_account_numbers() -> List[dict]

  • GET /accounts/accountNumbers
  • Returns accountNumber and hashValue pairs.
  • Example response:
[
  {"accountNumber":"12345678","hashValue":"827C...AC12"}
]

get_accounts(params: dict|None=None) -> dict

  • GET /accounts

  • Query parameters:

    • fields (optional): the API currently accepts positions to return positions. E.g.: fields=positions.

get_account_by_id(account_hash: str, params: dict|None=None) -> dict

  • GET /accounts/{accountNumber}

  • account_hash: encrypted account identifier (hashValue).

  • Query parameters:

    • fields (optional): positions to include positions. E.g.: fields=positions.

find_account(last_4_digits: str) -> dict|None

  • Helper that uses get_account_numbers() and filters by the last 4 digits, then calls get_account_by_id.

get_transactions(account_hash: str, from_date: str|None, to_date: str|None, filters: dict|None=None) -> dict

  • GET /accounts/{accountHash}/transactions

  • ONE DATE REQUIRED: you may pass only from_date or only to_date. If you pass a single date, the SDK fills in the other for the same day:

    • Short format YYYY-MM-DD: start → YYYY-MM-DDT00:00:00.000Z, end → YYYY-MM-DDT23:59:59.000Z
    • Full ISO UTC YYYY-MM-DDTHH:MM:SS.ffffffZ: used as-is; if the other date is missing, it is derived with 00:00:00.000Z or 23:59:59.000Z of the same day.
  • Params:

    • startDate: ISO UTC - YYYY-MM-DDTHH:MM:SS.ffffffZ (or short YYYY-MM-DD)

    • endDate: ISO UTC - YYYY-MM-DDTHH:MM:SS.ffffffZ (or short YYYY-MM-DD)

    • filters: optional dict:

      • types: string with valid types: TRADE, RECEIVE_AND_DELIVER, DIVIDEND_OR_INTEREST, ACH_RECEIPT, ACH_DISBURSEMENT, CASH_RECEIPT, CASH_DISBURSEMENT, ELECTRONIC_FUND, WIRE_OUT, WIRE_IN, JOURNAL
      • symbol: specific symbol
      • status: transaction status

    Note: In some API configurations, types may be considered mandatory. The SDK does not require it and treats it as an optional filter.

Correct example:

from datetime import datetime, timezone, timedelta

# Get hashValue
hash_value = client.account.get_account_numbers()[0]['hashValue']

# Create UTC dates
start = datetime.now(timezone.utc) - timedelta(days=7)
start = start.replace(hour=0, minute=0, second=0, microsecond=0)
end = datetime.now(timezone.utc).replace(hour=23, minute=59, second=59, microsecond=999999)

# Proper call
transactions = client.account.get_transactions(
    account_hash=hash_value,  # Use hashValue!
    from_date=start.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
    to_date=end.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
    filters={"types": "TRADE,DIVIDEND_OR_INTEREST"}  # Optional
)

get_transaction(account_hash: str, transaction_id: str) -> dict

  • GET /accounts/{accountHash}/transactions/{transactionId}

  • Path parameters:

    • account_hash (required)
    • transaction_id (required): numeric transaction ID
  • Returns details of a specific transaction.

get_user_preferences() -> dict

  • GET /userPreference

  • Returns user preferences and, when applicable, streamer information needed for WebSocket:

    • streamerSocketUrl
    • schwabClientCustomerId
    • schwabClientCorrelId
    • SchwabClientChannel
    • SchwabClientFunctionId
  • Useful to initialize client.streaming (LOGIN and subscriptions).


Orders (orders.py)

All responses include HTTP metadata and the native data:

{
  "status_code": 200,
  "success": true,
  "headers": {"...": "..."},
  "url": "https://...",
  "elapsed_seconds": 0.42,
  "method": "GET|POST|PUT|DELETE",
  "params": {"...": "..."},
  "data": {},
  "order_id": "..."   
}

get_orders(account_hash, from_entered_time=None, to_entered_time=None, status=None, max_results=None) -> dict

  • GET /accounts/{accountNumber}/orders
  • API requirements: fromEnteredTime and toEnteredTime are mandatory (ISO-8601). The SDK defaults them to "last 60 days" if you omit them.
  • status (case-insensitive): normalized to uppercase. Values accepted by the API: AWAITING_PARENT_ORDER, AWAITING_CONDITION, AWAITING_STOP_CONDITION, AWAITING_MANUAL_REVIEW, ACCEPTED, AWAITING_UR_OUT, PENDING_ACTIVATION, QUEUED, WORKING, REJECTED, PENDING_CANCEL, CANCELED, PENDING_REPLACE, REPLACED, FILLED, EXPIRED, NEW, AWAITING_RELEASE_TIME, PENDING_ACKNOWLEDGEMENT, PENDING_RECALL, UNKNOWN.
  • maxResults (optional): record limit (API default 3000).
  • Datetime format: ISO-8601 YYYY-MM-DDTHH:MM:SS.000Z.

get_all_orders(from_entered_time=None, to_entered_time=None, status=None, max_results=None) -> dict

  • GET /orders
  • API requirements: fromEnteredTime and toEnteredTime are mandatory (ISO-8601). If you omit them, the SDK uses "last 60 days".
  • Filters identical to get_orders (including status normalization).
  • maxResults (optional): record limit (API default 3000).

place_order(account_hash: str, order_data: dict) -> dict

  • POST /accounts/{accountNumber}/orders
  • Extracts order_id from the Location header when present.

get_order(account_hash: str, order_id: str) -> dict

  • GET /accounts/{accountNumber}/orders/{orderId}

cancel_order(account_hash: str, order_id: str) -> dict

  • DELETE /accounts/{accountNumber}/orders/{orderId}
  • Tries to extract order_id from Location if the server returns it.

replace_order(account_hash: str, order_id: str, new_order_data: dict) -> dict

  • PUT /accounts/{accountNumber}/orders/{orderId}
  • Returns new order_id (from Location) when applicable.

preview_order(account_hash: str, order_data: dict) -> dict

  • POST /accounts/{accountNumber}/previewOrder
  • Tries to extract order_id from Location if the server returns it.

Payload helpers

  • build_limit_order(symbol, quantity, price, instruction="BUY")
  • build_market_order(symbol, quantity, instruction="BUY")
  • build_bracket_order(symbol, quantity, entry_price, take_profit_price, stop_loss_price)

Example (preview):

acc = client.account.get_account_numbers()[0]['hashValue']
order = client.orders.build_limit_order("AAPL", 1, 100.00)
preview = client.orders.preview_order(acc, order)

Market Data (market.py)

get_quotes(symbols: str|List[str], params: dict|None=None) -> dict

  • GET /quotes?symbols=...

  • Parameters:

    • symbols (required): str or list of symbols separated by commas. E.g.: AAPL,AMZN,$DJI,/ESH23.

    • params (optional):

      • fields: subset of data. Values: quote, fundamental, extended, reference, regular. Default: all.
      • indicative: boolean (true|false) to include indicative quotes (e.g., ETFs). Example: indicative=false.

get_quote(symbol: str, params: dict|None=None) -> dict

  • GET /{symbol}/quotes

  • Parameters:

    • symbol (required): single symbol (e.g., TSLA).

    • params (optional):

      • fields: same as in get_quotes.

get_option_chain(symbol: str, params: dict|None=None) -> dict

  • GET /chains

  • Parameters:

    • symbol (required)
    • contractType (optional): CALL, PUT, ALL
    • strikeCount (optional): int (number of strikes above/below ATM)
    • includeUnderlyingQuote (optional): boolean
    • strategy (optional): SINGLE, ANALYTICAL, COVERED, VERTICAL, STRADDLE, etc.
    • interval (optional): number (interval between strikes for spreads)
    • strike (optional): number (specific strike)
    • range (optional): ITM, NTM, OTM, ALL
    • fromDate / toDate (optional): YYYY-MM-DD (the SDK also accepts ISO and trims to date)
    • volatility, underlyingPrice, interestRate, daysToExpiration (optional): for ANALYTICAL
    • expMonth (optional): JANDEC, ALL
    • optionType (optional)
    • entitlement (optional): customer type (PN, NP, PP)
  • GET /expirationchain

  • Parameters:

    • symbol (required)

get_price_history(symbol, periodType="month", period=1, frequencyType="daily", frequency=1, startDate=None, endDate=None, params=None) -> dict

  • GET /pricehistory

  • Parameters:

    • periodType: day|month|year|ytd
    • period: int
    • frequencyType: minute|daily|weekly|monthly
    • frequency: int
    • startDate/endDate (ms since epoch)
    • Additional optionals (needExtendedHoursData, etc., depending on entitlements)

get_movers(symbol_id: str, params: dict|None=None) -> dict

  • GET /movers/{symbol_id} (e.g., $DJI, $SPX, NASDAQ)

  • Parameters (if applicable):

    • sort: VOLUME, TRADES, PERCENT_CHANGE_UP, PERCENT_CHANGE_DOWN
    • frequency: 0,1,5,10,30,60 (min). Default 0.

get_markets(params: dict|None=None) -> dict

  • GET /markets

  • Query parameters:

    • markets (required by API): array of equity, option, bond, future, forex (the SDK accepts params={"markets": ...})
    • date (optional): YYYY-MM-DD (if you send ISO, the SDK trims to date)

get_market_hours(market_id: str, params: dict|None=None) -> dict

  • GET /markets/{market_id} (equity, option, bond, forex)

  • Query parameters:

    • date (optional): YYYY-MM-DD (if you send ISO, the SDK trims to date)

get_instruments(symbols: str|List[str], projection: str, extra_params: dict|None=None) -> dict

  • GET /instruments

  • Parameters:

    • symbols (required): single symbol or comma-separated list
    • projection (required by API): symbol-search, symbol-regex, desc-search, desc-regex, search, fundamental Note: the SDK does not require it in the signature, but it is recommended to provide it.

get_instrument_by_cusip(cusip_id: str, params: dict|None=None) -> dict

  • GET /instruments/{cusip_id}

WebSocket Streaming (streaming.py)

Callbacks

  • on_data(fn) (data frames)
  • on_response(fn) (command confirmations/errors)
  • on_notify(fn) (heartbeats/notices)

Basic flow

ws = client.streaming
ws.on_data(lambda f: print("DATA", f))
ws.on_response(lambda f: print("RESP", f))
ws.on_notify(lambda f: print("NOTIFY", f))
ws.connect(); ws.login()  # Authorization = access token without "Bearer"
ws.equities_subscribe(["AAPL","MSFT"])             # LEVELONE_EQUITIES
ws.options_subscribe(["AAPL  250926C00257500"])      # LEVELONE_OPTIONS
ws.nasdaq_book(["MSFT"])                             # NASDAQ_BOOK
ws.chart_equity(["AAPL"])                            # CHART_EQUITY
ws.screener_equity(["NYSE_VOLUME_5"])                # SCREENER_EQUITY

Key Formats (quick table)

Type Format Example Notes
Equities Ticker AAPL, MSFT Uppercase
Options RRRRRR␣␣YYMMDD[C/P]STRIKE AAPL  250926C00257500 Two spaces; zero-padded strike (8+ digits)
Futures /<root><month><yy> /ESZ25 Root/month/year in uppercase
FuturesOptions ./<root><month><year><C/P><strike> ./OZCZ23C565 Depends on the feed
Forex PAIR EUR/USD, USD/JPY / separator
Screener PREFIX_SORTFIELD_FREQUENCY NYSE_VOLUME_5 Prefix/criterion/frequency

Service-by-Service Examples

Level One Options

ws.options_subscribe(["AAPL  250926C00257500"])  # standard option string
# Default fields: 0,2,3,4,8,16,17,18,20,28,29,30,31,37,44

Level One Futures

ws.futures_subscribe(["/ESZ25"])  # E-mini S&P 500 Dec 2025
# Default fields: 0,1,2,3,4,5,8,12,13,18,19,20,24,33

Level One Forex

ws.forex_subscribe(["EUR/USD","USD/JPY"])  
# Default fields: 0,1,2,3,4,5,6,7,8,9,10,11,15,16,17,20,21,27,28,29

Book (Level II)

ws.nasdaq_book(["MSFT"])  # Also: ws.nyse_book, ws.options_book
# Default fields: 0 (Symbol), 1 (BookTime), 2 (Bids), 3 (Asks)

Chart (Series)

ws.chart_equity(["AAPL"])      # 0..7: key, open, high, low, close, volume, sequence, chartTime
ws.chart_futures(["/ESZ25"])   # 0..5

Screener

ws.screener_equity(["NYSE_VOLUME_5"])   
ws.screener_options(["CBOE_VOLUME_5"])

Account Activity

ws.account_activity()  # Gets accountHash and subscribes

Utilities

  • subscribe(service, keys, fields=None)
  • add(service, keys)
  • unsubscribe(service, keys) / unsubscribe_service(service, keys)
  • view(service, fields)

Recommended Fields

  • LEVELONE_EQUITIES: 0,1,2,3,4,5,8,10,18,42,33,34,35
  • LEVELONE_OPTIONS: 0,2,3,4,8,16,17,18,20,28,29,30,31,37,44
  • LEVELONE_FUTURES: 0,1,2,3,4,5,8,12,13,18,19,20,24,33
  • CHART_EQUITY: 0,1,2,3,4,5,6,7

Quick fields table (copy/paste)

Service Fields CSV
LEVELONE_EQUITIES 0,1,2,3,4,5,8,10,18,42,33,34,35
LEVELONE_OPTIONS 0,2,3,4,8,16,17,18,20,28,29,30,31,37,44
LEVELONE_FUTURES 0,1,2,3,4,5,8,12,13,18,19,20,24,33
LEVELONE_FUTURES_OPTIONS 0,1,2,3,4,5,8,12,13,18,19,20,24,33
LEVELONE_FOREX 0,1,2,3,4,5,6,7,8,9,10,11,15,16,17,20,21,27,28,29
NASDAQ_BOOK 0,1,2,3
NYSE_BOOK 0,1,2,3
OPTIONS_BOOK 0,1,2,3
CHART_EQUITY 0,1,2,3,4,5,6,7
CHART_FUTURES 0,1,2,3,4,5
SCREENER_EQUITY 0,1,2,3,4
SCREENER_OPTION 0,1,2,3,4
ACCT_ACTIVITY 0,1,2

SUBS / ADD / VIEW / UNSUBS examples by service

ws = client.streaming
ws.on_data(lambda f: print("DATA", f))
ws.on_response(lambda f: print("RESP", f))
ws.connect(); ws.login()

# LEVELONE_EQUITIES
ws.equities_subscribe(["AAPL","TSLA"], fields=[0,1,2,3,4,5,8,10,18,42,33,34,35])
ws.equities_add(["MSFT"])                          # adds without replacing
ws.equities_view([0,1,2,3,5,8,18])                  # changes fields
ws.equities_unsubscribe(["TSLA"])                  # removes symbols

# LEVELONE_OPTIONS
ws.options_subscribe(["AAPL  250926C00257500"], fields=[0,2,3,4,8,16,17,18,20,28,29,30,31,37,44])
ws.options_add(["AAPL  250926P00257500"])          
ws.options_view([0,2,3,4,8,16,17,20,28,29,30,31,37,44])
ws.options_unsubscribe(["AAPL  250926C00257500"])  

# LEVELONE_FUTURES
ws.futures_subscribe(["/ESZ25"], fields=[0,1,2,3,4,5,8,12,13,18,19,20,24,33])
ws.futures_add(["/NQZ25"])
ws.futures_view([0,1,2,3,4,5,8,12,13,18,19,20,24,33])
ws.futures_unsubscribe(["/ESZ25"])  

# BOOK (Level II)
ws.nasdaq_book(["MSFT"], fields=[0,1,2,3])
ws.add("NASDAQ_BOOK", ["AAPL"])                    # generic ADD
ws.view("NASDAQ_BOOK", [0,1,2,3])                   # generic VIEW
ws.unsubscribe_service("NASDAQ_BOOK", ["MSFT"])    # generic UNSUBS

# CHART (Series)
ws.chart_equity(["AAPL"], fields=[0,1,2,3,4,5,6,7])
ws.add("CHART_EQUITY", ["MSFT"])                  # generic ADD
ws.view("CHART_EQUITY", [0,1,2,3,4,5,6,7])          # generic VIEW
ws.unsubscribe("CHART_EQUITY", ["AAPL"])           # generic UNSUBS

# SCREENER
ws.screener_equity(["EQUITY_ALL_VOLUME_5"], fields=[0,1,2,3,4])
ws.add("SCREENER_EQUITY", ["NYSE_TRADES_1"])      
ws.view("SCREENER_EQUITY", [0,1,2,3,4])
ws.unsubscribe("SCREENER_EQUITY", ["EQUITY_ALL_VOLUME_5"])

# ACCT_ACTIVITY
ws.account_activity(fields=[0,1,2])                  # subscribe account activity
# For UNSUBS you need the same key used in SUBS (account_hash)
account_hash = getattr(client, "_account_hash", None)
if account_hash:
    ws.unsubscribe_service("ACCT_ACTIVITY", [account_hash])

Quick Field Guide (IDs → meaning)

Note: exact mappings may vary depending on entitlements/version. Below are practical equivalences observed in frames.

LEVELONE_EQUITIES

ID Field
0 symbol/key
1 bidPrice
2 askPrice
3 lastPrice
4 bidSize
5 askSize
8 totalVolume
10 referencePrice (open/mark)
18 netChange
42 percentChange

LEVELONE_OPTIONS

ID Field
0 symbol/key
2 bidPrice
3 askPrice
4 lastPrice
8 totalVolume
16 openInterest
17 daysToExpiration
20 strikePrice
28 delta
29 gamma
30 theta
31 vega
44 impliedVolatility (if provided)

LEVELONE_FUTURES

ID Field
0 symbol/key
1 bidPrice
2 askPrice
3 lastPrice
4 bidSize
5 askSize
8 totalVolume
12 openInterest
13 contractDepth/series info (per feed)
18 netChange
19 sessionChange (or days/indicator per feed)
20 percentChange/ratio (per feed)
24 lastSettlement/mark
33 priorSettle

Frame Structure

  • Confirmations (response): { "response": [ { "service":"ADMIN","command":"LOGIN","content":{"code":0,"msg":"..."}} ] }
  • Data (data): { "service":"LEVELONE_EQUITIES","timestamp":...,"command":"SUBS","content":[{"key":"AAPL",...}] }
  • Notifications (notify): { "notify": [ { "heartbeat": "..." } ] }

Streamer API Cheat Sheet (parameters and commands)

  1. Connection and prerequisites
  • Auth: use the Access Token from the OAuth flow.
  • Session IDs (from GET /userPreference): schwabClientCustomerId, schwabClientCorrelId, SchwabClientChannel, SchwabClientFunctionId.
  • Transport: JSON WebSocket. One stream per user (if you open more: code 12 CLOSE_CONNECTION).
  1. Envelope of each command
  • Common fields:

    • service (req.): ADMIN, LEVELONE_EQUITIES, LEVELONE_OPTIONS, LEVELONE_FUTURES, LEVELONE_FUTURES_OPTIONS, LEVELONE_FOREX, NYSE_BOOK, NASDAQ_BOOK, OPTIONS_BOOK, CHART_EQUITY, CHART_FUTURES, SCREENER_EQUITY, SCREENER_OPTION, ACCT_ACTIVITY.
    • command (req.): LOGIN, SUBS, ADD, UNSUBS, VIEW, LOGOUT.
    • requestid (req.): unique request identifier.
    • SchwabClientCustomerId and SchwabClientCorrelId (recommended): from userPreference.
    • parameters (optional): depends on service/command.
  • Notes: SUBS overwrites list; ADD appends; UNSUBS removes; VIEW changes fields.

  1. ADMIN (session)
  • LOGIN (service=ADMIN, command=LOGIN)

    • parameters: Authorization (token without "Bearer"), SchwabClientChannel, SchwabClientFunctionId.
  • LOGOUT (service=ADMIN, command=LOGOUT)

    • parameters: empty.
  1. LEVEL ONE (L1 quotes)
  • Common parameters: keys (req., CSV list), fields (optional, indexes).
  • LEVELONE_EQUITIES: keys uppercase tickers (e.g., AAPL,TSLA).
  • LEVELONE_OPTIONS: keys Schwab option format RRRRRR YYMMDD[C/P]STRIKE.
  • LEVELONE_FUTURES: keys /<root><monthCode><yearCode> (month codes: F,G,H,J,K,M,N,Q,U,V,X,Z; year two digits), e.g., /ESZ25.
  • LEVELONE_FUTURES_OPTIONS: keys ./<root><month><yy><C|P><strike>, e.g., ./OZCZ23C565.
  • LEVELONE_FOREX: keys BASE/QUOTE pairs CSV (e.g., EUR/USD,USD/JPY).
  1. BOOK (Level II)
  • Services: NYSE_BOOK, NASDAQ_BOOK, OPTIONS_BOOK.
  • Parameters: keys (req., tickers), fields (optional, level indexes).
  1. CHART (streaming series)
  • CHART_EQUITY: keys equities; fields indexes (OHLCV, time, seq).
  • CHART_FUTURES: keys futures (same format as L1 futures); fields indexes.
  1. SCREENER (gainers/losers/actives)
  • Services: SCREENER_EQUITY, SCREENER_OPTION.

  • keys pattern PREFIX_SORTFIELD_FREQUENCY, e.g., EQUITY_ALL_VOLUME_5.

    • PREFIX examples: $COMPX, $DJI, $SPX, INDEX_ALL, NYSE, NASDAQ, OTCBB, EQUITY_ALL, OPTION_PUT, OPTION_CALL, OPTION_ALL.
    • SORTFIELD: VOLUME, TRADES, PERCENT_CHANGE_UP, PERCENT_CHANGE_DOWN, AVERAGE_PERCENT_VOLUME.
    • FREQUENCY: 0,1,5,10,30,60 (min; 0 = full day).
  • fields (optional): screener field indexes.

  1. ACCOUNT (account activity)
  • Service: ACCT_ACTIVITY (SUBS/UNSUBS).
  • keys (req.): arbitrary identifier for your sub; if you send multiple, the first is used.
  • fields (recommended): 0 (or 0,1,2,3 per example/need).
  1. Server responses
  • Types: response (to your requests), notify (heartbeats), data (market flow).
  • Key codes: 0 SUCCESS, 3 LOGIN_DENIED, 11 SERVICE_NOT_AVAILABLE, 12 CLOSE_CONNECTION, 19 REACHED_SYMBOL_LIMIT, 20 STREAM_CONN_NOT_FOUND, 21 BAD_COMMAND_FORMAT, 26/27/28/29 successes for SUBS/UNSUBS/ADD/VIEW.
  1. Delivery Types
  • All Sequence: everything with sequence number.
  • Change: only changed fields (conflated).
  • Whole: full messages with throttling.
  1. Best practices
  • Do LOGIN and wait for code=0 before SUBS/ADD.
  • To add symbols without losing existing ones, use ADD (not SUBS).
  • Change fields with VIEW for performance.
  • Handle notify (heartbeats) and reconnect if they are lost.
  • Reuse your SchwabClientCorrelId during the session.
  • If you see 19 (symbol limit), shard loads by service/session.

Advanced Troubleshooting

  • notify.code=12 (Only one connection): close other active WebSocket sessions.
  • response.content.code=3 (Login denied): invalid/expired token → client.login().
  • response.content.code=21 (Bad command formatting): check Authorization format (without Bearer) and keys (spacing for options, uppercase).
  • Persistent REST 401: delete schwab_tokens.json and re-run client.login().
  • High latency/lost frames: avoid parallel reconnects; use the SDK's auto-resubscription.

Contributions

Your contributions are welcome! Ideas, issues, and PRs help improve the SDK:

  • Open an issue with clear details (environment, steps, expected/actual error).
  • Propose endpoint coverage improvements and examples.
  • Follow a clear style and add tests or minimal examples when possible.

If you want to hold working sessions or discuss the roadmap, open an issue labeled discussion.

Disclaimer

This project is unofficial and is not affiliated with, sponsored by, or endorsed by Charles Schwab & Co., Inc. “Schwab” and other trademarks are the property of their respective owners. Use of this SDK is subject to the terms and conditions of Schwab APIs and applicable regulations. Use at your own discretion and responsibility.


License

MIT (LICENSE)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

schwab_sdk_unofficial-0.1.2.tar.gz (50.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

schwab_sdk_unofficial-0.1.2-py3-none-any.whl (49.1 kB view details)

Uploaded Python 3

File details

Details for the file schwab_sdk_unofficial-0.1.2.tar.gz.

File metadata

  • Download URL: schwab_sdk_unofficial-0.1.2.tar.gz
  • Upload date:
  • Size: 50.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for schwab_sdk_unofficial-0.1.2.tar.gz
Algorithm Hash digest
SHA256 165a4338b3a1ca2f643e8e499d2e939a75286e7a0434eb895545584bdb2df3a8
MD5 f794861f89ad814b542d7f7442b96f88
BLAKE2b-256 e8a8c5a9efc5174758b4748012d7c318d1150c08aa09d13e61870520b71cf23d

See more details on using hashes here.

File details

Details for the file schwab_sdk_unofficial-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for schwab_sdk_unofficial-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 6894b67d668129eaa963703c748d062aab59080ea50257083fc823cb39797d22
MD5 2d3d3869e3cdfab4b2a3817ab2063c79
BLAKE2b-256 6b890aeb3252f75ee9792cb8713f1ccd44ff779318d69d90d25fbc90975ae6da

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page