| name | schwab-api |
| description | Schwab API integration patterns including dual OAuth2 authentication, token management, market data retrieval, and order execution. Use when working on Schwab auth, tokens, API calls, market data, order placement, or any code touching schwab_auth, DualSchwabAuth, run_dual_auth, or token refresh. |
Schwab API Integration
Canonical Contract
This is the canonical Schwab skill contract for TradingBot.
- OpenClaw/operator-specific usage lives in
schwab_skill/SKILL.md and must
remain consistent with this canonical contract.
- If behavior differs between documents, this file takes precedence for
repository architecture and implementation guidance.
Dual OAuth Architecture
The system uses two separate Schwab developer apps with distinct permissions:
| Session | Purpose | Env Prefix | Token File |
|---|
| Market | Quotes, chains, movers, price history | SCHWAB_MARKET_* | tokens_market.enc |
| Account | Positions, orders, account info | SCHWAB_ACCOUNT_* | tokens_account.enc |
Why two apps: Schwab's API requires different OAuth scopes. Splitting them isolates market data access from account-mutating operations.
Credential Environment Variables
SCHWAB_MARKET_APP_KEY=...
SCHWAB_MARKET_APP_SECRET=...
SCHWAB_ACCOUNT_APP_KEY=...
SCHWAB_ACCOUNT_APP_SECRET=...
SCHWAB_CALLBACK_URL=https://127.0.0.1:8182
SCHWAB_MARKET_CALLBACK_URL=https://127.0.0.1:8182 # optional, falls back to SCHWAB_CALLBACK_URL
SCHWAB_TOKEN_ENCRYPTION_KEY=... # optional, for encrypted token files
SaaS adds: CREDENTIAL_ENCRYPTION_KEY for per-user tokens stored in user_credentials table.
Auth Flow
Local (single user)
python run_dual_auth.py opens browser for both apps
- User completes OAuth in browser, tokens saved as encrypted files
DualSchwabAuth auto-refreshes tokens on expiry
SaaS (multi-tenant)
- Dashboard generates authorize URLs:
GET /api/oauth/schwab/{account|market}/authorize-url
- User completes OAuth, callback stores encrypted tokens in DB
- Per-tenant
DualSchwabAuth created from DB credentials
Token Refresh
DualSchwabAuth handles automatic refresh. If tokens expire completely:
- Local: re-run
run_dual_auth.py
- SaaS: user reconnects via dashboard OAuth flow
Market Data Resilience
Priority chain in market_data.py:
- Schwab API — primary, with retry/backoff and circuit breaker
- yfinance — fallback for price history
- Polygon — secondary fallback where implemented
Circuit breaker trips after consecutive failures, auto-recovers after cooldown.
Order Execution Flow
- Signal passes all quality gates and guardrails
- Position size computed (adaptive stops, regime multiplier, sector caps)
- Order submitted via account session
- Stop-loss order attached on fill confirmation
- All execution logged to DB and Discord
Health Checks
GET /api/health/deep — checks token validity, quote freshness, DB connectivity
python healthcheck.py — local CLI diagnostic
- Both report:
market_token_ok, account_token_ok, last_quote_age_sec
Key Files
schwab_skill/schwab_auth.py — DualSchwabAuth class
schwab_skill/run_dual_auth.py — browser OAuth flow
schwab_skill/market_data.py — data retrieval with fallbacks
schwab_skill/execution.py — order placement
schwab_skill/webapp/tenant_dashboard.py — SaaS OAuth callbacks