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
accountNumberandhashValuepairs. - Example response:
[
{"accountNumber":"12345678","hashValue":"827C...AC12"}
]
get_accounts(params: dict|None=None) -> dict
-
GET
/accounts -
Query parameters:
fields(optional): the API currently acceptspositionsto 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):positionsto 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 callsget_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_dateor onlyto_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 with00:00:00.000Zor23:59:59.000Zof the same day.
- Short format
-
Params:
-
startDate: ISO UTC -YYYY-MM-DDTHH:MM:SS.ffffffZ(or shortYYYY-MM-DD) -
endDate: ISO UTC -YYYY-MM-DDTHH:MM:SS.ffffffZ(or shortYYYY-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,JOURNALsymbol: specific symbolstatus: transaction status
Note: In some API configurations,
typesmay 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:
streamerSocketUrlschwabClientCustomerIdschwabClientCorrelIdSchwabClientChannelSchwabClientFunctionId
-
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:
fromEnteredTimeandtoEnteredTimeare 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:
fromEnteredTimeandtoEnteredTimeare mandatory (ISO-8601). If you omit them, the SDK uses "last 60 days". - Filters identical to
get_orders(includingstatusnormalization). maxResults(optional): record limit (API default 3000).
place_order(account_hash: str, order_data: dict) -> dict
- POST
/accounts/{accountNumber}/orders - Extracts
order_idfrom theLocationheader 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_idfromLocationif 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(fromLocation) when applicable.
preview_order(account_hash: str, order_data: dict) -> dict
- POST
/accounts/{accountNumber}/previewOrder - Tries to extract
order_idfromLocationif 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 inget_quotes.
-
get_option_chain(symbol: str, params: dict|None=None) -> dict
-
GET
/chains -
Parameters:
symbol(required)contractType(optional):CALL,PUT,ALLstrikeCount(optional): int (number of strikes above/below ATM)includeUnderlyingQuote(optional): booleanstrategy(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,ALLfromDate/toDate(optional):YYYY-MM-DD(the SDK also accepts ISO and trims to date)volatility,underlyingPrice,interestRate,daysToExpiration(optional): forANALYTICALexpMonth(optional):JAN–DEC,ALLoptionType(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|ytdperiod: intfrequencyType:minute|daily|weekly|monthlyfrequency: intstartDate/endDate(ms since epoch)- Additional optionals (
needExtendedHoursData, etc., depending on entitlements)
get_movers(symbol_id: str, sort: str|None=None, frequency: int|None=None, params: dict|None=None) -> dict
-
GET
/movers/{symbol_id}(e.g.,$DJI,$SPX,NASDAQ) -
Parameters:
symbol_id(required): Index Symbol. Available values:$DJI,$COMPX,$SPX,NYSE,NASDAQ,OTCBB,INDEX_ALL,EQUITY_ALL,OPTION_ALL,OPTION_PUT,OPTION_CALLsort(optional): Sort by a particular attribute. Available values:VOLUME,TRADES,PERCENT_CHANGE_UP,PERCENT_CHANGE_DOWNfrequency(optional): To return movers with the specified directions of up or down. Available values:0,1,5,10,30,60(min). Default0params(optional): Additional query parameters
get_markets(params: dict|None=None) -> dict
-
GET
/markets -
Query parameters:
markets(required by API): array ofequity,option,bond,future,forex(the SDK acceptsparams={"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 listprojection(required by API):symbol-search,symbol-regex,desc-search,desc-regex,search,fundamentalNote: 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)
- 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).
- 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.SchwabClientCustomerIdandSchwabClientCorrelId(recommended): fromuserPreference.parameters(optional): depends on service/command.
-
Notes:
SUBSoverwrites list;ADDappends;UNSUBSremoves;VIEWchangesfields.
- ADMIN (session)
-
LOGIN(service=ADMIN,command=LOGIN)- parameters:
Authorization(token without "Bearer"),SchwabClientChannel,SchwabClientFunctionId.
- parameters:
-
LOGOUT(service=ADMIN,command=LOGOUT)- parameters: empty.
- LEVEL ONE (L1 quotes)
- Common parameters:
keys(req., CSV list),fields(optional, indexes). LEVELONE_EQUITIES:keysuppercase tickers (e.g.,AAPL,TSLA).LEVELONE_OPTIONS:keysSchwab option formatRRRRRR 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:keysBASE/QUOTEpairs CSV (e.g.,EUR/USD,USD/JPY).
- BOOK (Level II)
- Services:
NYSE_BOOK,NASDAQ_BOOK,OPTIONS_BOOK. - Parameters:
keys(req., tickers),fields(optional, level indexes).
- CHART (streaming series)
CHART_EQUITY:keysequities;fieldsindexes (OHLCV, time, seq).CHART_FUTURES:keysfutures (same format as L1 futures);fieldsindexes.
- SCREENER (gainers/losers/actives)
-
Services:
SCREENER_EQUITY,SCREENER_OPTION. -
keyspatternPREFIX_SORTFIELD_FREQUENCY, e.g.,EQUITY_ALL_VOLUME_5.PREFIXexamples:$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.
- 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(or0,1,2,3per example/need).
- Server responses
- Types:
response(to your requests),notify(heartbeats),data(market flow). - Key codes:
0SUCCESS,3LOGIN_DENIED,11SERVICE_NOT_AVAILABLE,12CLOSE_CONNECTION,19REACHED_SYMBOL_LIMIT,20STREAM_CONN_NOT_FOUND,21BAD_COMMAND_FORMAT,26/27/28/29successes forSUBS/UNSUBS/ADD/VIEW.
- Delivery Types
All Sequence: everything with sequence number.Change: only changed fields (conflated).Whole: full messages with throttling.
- Best practices
- Do
LOGINand wait forcode=0beforeSUBS/ADD. - To add symbols without losing existing ones, use
ADD(notSUBS). - Change
fieldswithVIEWfor performance. - Handle
notify(heartbeats) and reconnect if they are lost. - Reuse your
SchwabClientCorrelIdduring 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): checkAuthorizationformat (withoutBearer) and keys (spacing for options, uppercase).- Persistent REST 401: delete
schwab_tokens.jsonand re-runclient.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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file schwab_sdk_unofficial-0.1.3.tar.gz.
File metadata
- Download URL: schwab_sdk_unofficial-0.1.3.tar.gz
- Upload date:
- Size: 51.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9d0a31ee018cbb5373e47c4aca1e655590b684c7a92a7ef0a07e5478fb4458c
|
|
| MD5 |
3ea7942a0e70e6a084f60b78e376ad80
|
|
| BLAKE2b-256 |
7368db9faad68f021aae8da597ce8719bfa5f6920e2f0b75363916a6df3abbd5
|
File details
Details for the file schwab_sdk_unofficial-0.1.3-py3-none-any.whl.
File metadata
- Download URL: schwab_sdk_unofficial-0.1.3-py3-none-any.whl
- Upload date:
- Size: 49.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
591d944954f87e1f64a2738209765b966d01dee1dc12df03343b928b0b7120d1
|
|
| MD5 |
b0d870d7f9d8d40b05b487b314cb98a8
|
|
| BLAKE2b-256 |
ec02be473595a0e212ad59df726c96408f90a13805ae5228411a257a07cd56dc
|