@sudowealth/schwab-api v2.1.0
Schwab API Client
TypeScript client for Charles Schwab API with OAuth support, market data, trading functionality, and complete type safety.
Unofficial Library
This is an unofficial, community-developed TypeScript client library for interacting with Schwab APIs. It has not been approved, endorsed, or certified by Charles Schwab. It is provided as-is, and its functionality may be incomplete or unstable. Use at your own risk, especially when dealing with financial data or transactions.
Getting Started
To use the Schwab API, you'll need to register for a developer account:
- Visit Schwab Developer Portal
- Sign up for a developer account
- Create an application to obtain your client ID and secret
- Review the API documentation and usage limits
Features
- OAuth Helper: Client-credentials OAuth flow with automatic token handling
- Request Pipeline: Middleware system for auth, rate limits, and retries
- Type Safety: Complete TypeScript definitions for all API endpoints
- Zod Validation: Runtime schema validation for API responses
- Market Data: Real-time quotes, price history, options chains, market hours, and movers
- Trading: Account management, order placement, transaction history, and user preferences
Installation
Available on npm:
npm install @sudowealth/schwab-api
Quick Start
Prerequisites:
- You must have a Schwab developer account. You can register at @https://developer.schwab.com/register.
- Create an application at @https://developer.schwab.com/dashboard/apps.
- In your application settings, provide your callback URL.
- Obtain the Client ID (App Key) and Client Secret from your application page. These will be used as environment variables (e.g.,
SCHWAB_CLIENT_ID
andSCHWAB_CLIENT_SECRET
).
The quickest way to get started is by using createSchwabAuth
along with
createApiClient
. This example demonstrates using a static access token. For
more robust authentication, such as OAuth 2.0 Code Flow, refer to the "Usage
Examples" section.
Basic Setup
import {
createSchwabAuthClient,
configureSchwabApi,
} from '@sudowealth/schwab-api'
// Create unified auth client
const auth = createSchwabAuthClient({
clientId: process.env.SCHWAB_ID,
clientSecret: process.env.SCHWAB_SECRET,
redirectUri: 'https://example.com/callback',
})
// Generate login URL
console.log('Visit:', auth.getAuthorizationUrl().authUrl)
// Exchange auth code for tokens
const tokens = await auth.exchangeCode('<authorization-code>')
// Create API client
const schwab = await configureSchwabApi({
tokens: {
current: () => tokens,
refresh: () => auth.refresh(tokens.refresh_token),
},
})
Market Data
// Get real-time quotes
const quotes = await schwab.marketData.quotes.getQuotes({
symbols: 'AAPL,MSFT,GOOGL',
fields: 'quote,fundamental',
})
// Get price history
const history = await schwab.marketData.priceHistory.getPriceHistory({
symbol: 'AAPL',
periodType: 'day',
period: 10,
frequencyType: 'minute',
frequency: 1,
})
// Get options chain
const options = await schwab.marketData.options.getOptionChain({
symbol: 'AAPL',
contractType: 'CALL',
strikeCount: 10,
})
// Get market hours
const hours = await schwab.marketData.marketHours.getMarketHours({
markets: 'equity,option',
})
// Get movers
const movers = await schwab.marketData.movers.getMovers({
index: '$SPX.X',
direction: 'up',
change: 'percent',
})
// Search instruments
const instruments = await schwab.marketData.instruments.getInstruments({
symbol: 'AAPL',
projection: 'symbol-search',
})
Trading
// Get accounts
const accounts = await schwab.trader.accounts.getAccounts()
// Get account details
const account = await schwab.trader.accounts.getAccount({
accountId: 'your-account-hash',
fields: 'positions',
})
// Get orders
const orders = await schwab.trader.orders.getOrders({
accountId: 'your-account-hash',
maxResults: 50,
})
// Place an order
const orderResponse = await schwab.trader.orders.placeOrder({
accountId: 'your-account-hash',
orderType: 'MARKET',
session: 'NORMAL',
duration: 'DAY',
orderStrategyType: 'SINGLE',
orderLegCollection: [
{
instruction: 'BUY',
quantity: 10,
instrument: {
symbol: 'AAPL',
assetType: 'EQUITY',
},
},
],
})
// Get transactions
const transactions = await schwab.trader.transactions.getTransactions({
accountId: 'your-account-hash',
type: 'TRADE',
startDate: '2024-01-01',
endDate: '2024-12-31',
})
// Get user preferences
const preferences = await schwab.trader.userPreference.getUserPreference()
Advanced Configuration with Middleware
import {
configureSchwabApi,
withRateLimit,
withRetry,
} from '@sudowealth/schwab-api'
const schwab = await configureSchwabApi({
tokens: {
current: () => tokens,
refresh: () => auth.refresh(tokens.refresh_token),
},
middlewares: [
withRateLimit(120, 60000), // 120 requests per minute
withRetry({ max: 3, baseMs: 1000 }), // Retry with exponential backoff
],
})
Important Notes
Token Management
The auth client provides a unified interface for OAuth operations:
getAuthorizationUrl()
: Generate URL for user loginexchangeCode(code)
: Exchange authorization code for tokensrefresh(refreshToken)
: Refresh expired access tokens
Refresh Token Expiration
Important: Schwab refresh tokens have a hard 7-day expiration limit that cannot be extended. This is a security measure enforced by Schwab's API servers.
When a refresh token expires:
- The
refresh()
method will throw aSchwabAuthError
with codeTOKEN_EXPIRED
- The user must complete a full re-authentication flow through Schwab's login page
- There is no way to refresh tokens indefinitely without user interaction
Handling Token Expiration
try {
const newTokens = await auth.refresh(oldRefreshToken)
// Update stored tokens
} catch (error) {
if (error instanceof SchwabAuthError && error.code === 'TOKEN_EXPIRED') {
// Redirect user to re-authenticate
const { authUrl } = auth.getAuthorizationUrl()
window.location.href = authUrl
}
}
API Structure
The API client is organized into logical namespaces:
marketData
: Real-time and historical market dataquotes
: Real-time quotes and fundamentalspriceHistory
: Historical price data and chartsoptions
: Options chains and pricingmarketHours
: Trading hours for different marketsmovers
: Top gaining/losing securitiesinstruments
: Security search and lookup
trader
: Account and trading operationsaccounts
: Account information and positionsorders
: Order management and executiontransactions
: Transaction history and detailsuserPreference
: User settings and preferences
Security Best Practices
Token Storage
⚠️ NEVER store tokens in plain text. Always encrypt sensitive data before storage.
// ❌ BAD - Insecure plain text storage
const insecureStorage = {
save: async (tokens) => {
await fs.writeFile('tokens.json', JSON.stringify(tokens))
},
load: async () => {
const data = await fs.readFile('tokens.json', 'utf-8')
return JSON.parse(data)
},
}
// ✅ GOOD - Encrypted storage example
import crypto from 'crypto'
const secureStorage = {
save: async (tokens) => {
// Use a secure key management system in production
const key = process.env.ENCRYPTION_KEY
const iv = crypto.randomBytes(16)
const cipher = crypto.createCipheriv(
'aes-256-gcm',
Buffer.from(key, 'hex'),
iv,
)
let encrypted = cipher.update(JSON.stringify(tokens), 'utf8', 'hex')
encrypted += cipher.final('hex')
const authTag = cipher.getAuthTag()
await secureStore.set('tokens', {
encrypted,
iv: iv.toString('hex'),
authTag: authTag.toString('hex'),
})
},
load: async () => {
const data = await secureStore.get('tokens')
if (!data) return null
const key = process.env.ENCRYPTION_KEY
const decipher = crypto.createDecipheriv(
'aes-256-gcm',
Buffer.from(key, 'hex'),
Buffer.from(data.iv, 'hex'),
)
decipher.setAuthTag(Buffer.from(data.authTag, 'hex'))
let decrypted = decipher.update(data.encrypted, 'hex', 'utf8')
decrypted += decipher.final('utf8')
return JSON.parse(decrypted)
},
}
Credential Management
- Never commit credentials: Keep
.env
files in.gitignore
- Use environment variables: Store sensitive data in environment variables or secure vaults
- Rotate credentials regularly: Implement a credential rotation policy
- Principle of least privilege: Only grant the minimum required permissions
# .env (never commit this file)
SCHWAB_CLIENT_ID=your-client-id
SCHWAB_CLIENT_SECRET=your-client-secret
ENCRYPTION_KEY=your-256-bit-hex-key
Security Checklist
- Use HTTPS for all API communications
- Encrypt tokens before storing them
- Never log tokens or sensitive data
- Implement proper error handling that doesn't leak information
- Use secure key management (AWS KMS, Azure Key Vault, etc.)
- Monitor for suspicious activity
- Implement request signing if available
- Keep dependencies up to date
Common Security Mistakes to Avoid
Logging Sensitive Data
// ❌ NEVER log tokens console.log('Access token:', tokens.access_token) // ✅ Log only non-sensitive metadata console.log('Token refreshed successfully')
Storing Secrets in Code
// ❌ NEVER hardcode secrets const clientSecret = 'abc123-secret-key' // ✅ Use environment variables const clientSecret = process.env.SCHWAB_CLIENT_SECRET
Exposing Error Details
// ❌ Don't expose internal details catch (error) { res.json({ error: error.stack }) } // ✅ Return generic error messages catch (error) { console.error('Internal error:', error) // Log internally res.json({ error: 'Authentication failed' }) // Generic response }
Error Handling
import { SchwabApiError, SchwabAuthError } from '@sudowealth/schwab-api'
try {
await schwab.trader.accounts.getAccounts()
} catch (error) {
if (error instanceof SchwabAuthError) {
if (error.code === 'TOKEN_EXPIRED') {
// Handle expired tokens
}
} else if (error instanceof SchwabApiError) {
// Handle API errors
console.error('API Error:', error.message)
}
}
Development
- Clone the repository
- Install dependencies:
npm install
- Build:
npm run build
- Lint:
npm run lint
- Type check:
npm run typecheck
- Format:
npm run format
- Validate all:
npm run validate
Installing Beta Versions
To install the latest beta release:
npm install @sudowealth/schwab-api@beta
License
MIT
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago