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
and SCHWAB_CLIENT_SECRET).
The quickest way to get started is by using createSchwabAuth along with
createApiClient. The examples below use the OAuth 2.0 Code Flow.
Basic Setup
import { createSchwabAuth, createApiClient } from '@sudowealth/schwab-api'
const auth = createSchwabAuth({
oauthConfig: {
clientId: process.env.SCHWAB_CLIENT_ID!,
clientSecret: process.env.SCHWAB_CLIENT_SECRET!,
redirectUri: 'https://example.com/callback',
},
})
console.log('Visit:', auth.getAuthorizationUrl().authUrl)
const tokens = await auth.exchangeCode('<authorization-code>')
const schwab = createApiClient({ auth })
Market Data
const quotes = await schwab.marketData.quotes.getQuotes({
queryParams: {
symbols: 'AAPL,MSFT,GOOGL',
fields: 'quote,fundamental',
},
})
const history = await schwab.marketData.priceHistory.getPriceHistory({
queryParams: {
symbol: 'AAPL',
periodType: 'day',
period: 10,
frequencyType: 'minute',
frequency: 1,
},
})
const options = await schwab.marketData.options.getOptionChain({
queryParams: {
symbol: 'AAPL',
contractType: 'CALL',
strikeCount: 10,
},
})
const hours = await schwab.marketData.marketHours.getMarketHours({
queryParams: {
markets: ['equity', 'option'],
},
})
const movers = await schwab.marketData.movers.getMovers({
pathParams: { symbol_id: '$SPX' },
queryParams: { sort: 'up', frequency: 0 },
})
const instruments = await schwab.marketData.instruments.getInstruments({
queryParams: {
symbol: 'AAPL',
projection: 'symbol-search',
},
})
Trading
const accounts = await schwab.trader.accounts.getAccounts()
const account = await schwab.trader.accounts.getAccountByNumber({
pathParams: { accountNumber: 'your-account-hash' },
queryParams: { fields: 'positions' },
})
const orders = await schwab.trader.orders.getOrdersByAccount({
pathParams: { accountNumber: 'your-account-hash' },
queryParams: { maxResults: 50 },
})
const orderResponse = await schwab.trader.orders.placeOrderForAccount({
pathParams: { accountNumber: 'your-account-hash' },
body: {
orderType: 'MARKET',
session: 'NORMAL',
duration: 'DAY',
orderStrategyType: 'SINGLE',
orderLegCollection: [
{
instruction: 'BUY',
quantity: 10,
instrument: {
symbol: 'AAPL',
assetType: 'EQUITY',
},
},
],
},
})
const transactions = await schwab.trader.transactions.getTransactions({
pathParams: { accountNumber: 'your-account-hash' },
queryParams: {
types: 'TRADE',
startDate: '2024-01-01',
endDate: '2024-12-31',
},
})
const preferences = await schwab.trader.userPreference.getUserPreference()
Advanced Configuration with Middleware
import { createSchwabAuth, createApiClient } from '@sudowealth/schwab-api'
const auth = createSchwabAuth({
oauthConfig: {
clientId: process.env.SCHWAB_CLIENT_ID!,
clientSecret: process.env.SCHWAB_CLIENT_SECRET!,
redirectUri: 'https://example.com/callback',
},
})
const schwab = createApiClient({
auth,
middleware: {
rateLimit: { maxRequests: 120, windowMs: 60_000 },
retry: { maxAttempts: 3, baseDelayMs: 1000 },
},
})
Important Notes
Token Management
The auth client provides a unified interface for OAuth operations:
getAuthorizationUrl(): Generate URL for user login
exchangeCode(code): Exchange authorization code for tokens
refresh(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 a SchwabAuthError with code
TOKEN_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)
} catch (error) {
if (error instanceof SchwabAuthError && error.code === 'TOKEN_EXPIRED') {
const { authUrl } = auth.getAuthorizationUrl()
window.location.href = authUrl
}
}
API Structure
The API client is organized into logical namespaces:
Security Best Practices
Token Storage
⚠️ NEVER store tokens in plain text. Always encrypt sensitive data before
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)
},
}
import crypto from 'crypto'
const secureStorage = {
save: async (tokens) => {
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
SCHWAB_CLIENT_ID=your-client-id
SCHWAB_CLIENT_SECRET=your-client-secret
ENCRYPTION_KEY=your-256-bit-hex-key
Security Checklist
Common Security Mistakes to Avoid
-
Logging Sensitive Data
console.log('Access token:', tokens.access_token)
console.log('Token refreshed successfully')
-
Storing Secrets in Code
const clientSecret = 'abc123-secret-key'
const clientSecret = process.env.SCHWAB_CLIENT_SECRET
-
Exposing Error Details
catch (error) {
res.json({ error: error.stack })
}
catch (error) {
console.error('Internal error:', error)
res.json({ error: 'Authentication failed' })
}
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') {
}
} else if (error instanceof SchwabApiError) {
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