A framework-agnostic TypeScript client for the TopstepX trading API with REST and WebSocket support.
npm install topstepx-api
Create a .env file in your project root:
TOPSTEP_USERNAME=your_username
TOPSTEP_API_KEY=your_api_key
Get your API key from your TopstepX account settings.
import { TopstepXClient, OrderType, OrderSide } from 'topstepx-api';
const client = new TopstepXClient({
username: process.env.TOPSTEP_USERNAME!,
apiKey: process.env.TOPSTEP_API_KEY!,
});
await client.connect();
// Get accounts
const accounts = await client.accounts.search({ onlyActiveAccounts: true });
console.log('Accounts:', accounts);
// Place a market order
const order = await client.orders.place({
accountId: accounts[0].id,
contractId: 'CON.F.US.ENQ.M25',
type: OrderType.Market,
side: OrderSide.Buy,
size: 1,
});
console.log('Order placed:', order.orderId);
// Disconnect when done
await client.disconnect();
The main client class that provides access to all APIs.
const client = new TopstepXClient({
username: string; // Required: TopstepX username
apiKey: string; // Required: TopstepX API key
baseUrl?: string; // Optional: API base URL (default: https://api.topstepx.com)
marketHubUrl?: string; // Optional: Market WebSocket URL
userHubUrl?: string; // Optional: User WebSocket URL
autoRefresh?: boolean; // Optional: Auto-refresh tokens (default: true)
tokenValidityHours?: number; // Optional: Token validity period (default: 24)
});
| Method | Description |
|---|---|
connect() |
Authenticate and establish WebSocket connections |
disconnect() |
Close all connections and cleanup |
getToken() |
Get the current session token |
isConnected |
Check if WebSocket connections are active |
client.on('connected', () => console.log('Connected'));
client.on('disconnected', () => console.log('Disconnected'));
client.on('error', (error) => console.error('Error:', error));
Access via client.accounts
Search for accounts.
const accounts = await client.accounts.search({
onlyActiveAccounts: boolean;
});
// Returns: Account[]
interface Account {
id: number;
name: string;
canTrade: boolean;
isVisible: boolean;
}
Access via client.orders
Place a new order.
import { OrderType, OrderSide } from 'topstepx-api';
const result = await client.orders.place({
accountId: number;
contractId: string;
type: OrderType; // Market, Limit, Stop, StopLimit
side: OrderSide; // Buy, Sell
size: number;
limitPrice?: number; // Required for Limit/StopLimit
stopPrice?: number; // Required for Stop/StopLimit
trailPrice?: number; // Optional trailing stop price
customTag?: string; // Optional custom identifier
linkedOrderId?: number; // Optional linked order (OCO)
});
// Returns: { orderId: number, success: boolean, ... }
Cancel an existing order.
await client.orders.cancel({
accountId: number;
orderId: number;
});
Modify an existing order.
await client.orders.modify({
accountId: number;
orderId: number;
size?: number;
limitPrice?: number;
stopPrice?: number;
trailPrice?: number;
});
Search historical orders.
const orders = await client.orders.search({
accountId: number;
startTimestamp?: string; // ISO 8601 format
endTimestamp?: string;
});
// Returns: Order[]
interface Order {
id: number;
accountId: number;
contractId: string;
creationTimestamp: string;
updateTimestamp: string | null;
status: OrderStatus;
type: OrderType;
side: OrderSide;
size: number;
limitPrice: number | null;
stopPrice: number | null;
}
Get currently open orders.
const openOrders = await client.orders.searchOpen({
accountId: number;
});
Access via client.positions
Get open positions.
const positions = await client.positions.searchOpen({
accountId: number;
});
// Returns: Position[]
interface Position {
id: number;
accountId: number;
contractId: string;
creationTimestamp: string;
type: PositionType; // Long, Short
size: number;
averagePrice: number;
}
Close a position entirely.
await client.positions.close({
accountId: number;
contractId: string;
});
Partially close a position.
await client.positions.partialClose({
accountId: number;
contractId: string;
size: number; // Number of contracts to close
});
Access via client.trades
Search trade history.
const trades = await client.trades.search({
accountId: number;
startTimestamp: string; // ISO 8601 format
endTimestamp: string;
});
// Returns: Trade[]
interface Trade {
id: number;
accountId: number;
contractId: string;
creationTimestamp: string;
price: number;
profitAndLoss: number | null;
fees: number;
side: OrderSide;
size: number;
voided: boolean;
orderId: number;
}
Access via client.contracts
Search for contracts/symbols.
const contracts = await client.contracts.search({
searchText: string; // e.g., "ES", "NQ", "CL"
live: boolean; // true for live, false for sim
});
// Returns: Contract[]
interface Contract {
id: string;
name: string;
description: string;
tickSize: number;
tickValue: number;
activeContract: boolean;
}
Get a specific contract by ID.
const contract = await client.contracts.searchById({
contractId: string; // e.g., "CON.F.US.ENQ.M25"
live: boolean;
});
// Returns: Contract | null
Access via client.history
Get historical OHLCV bars.
import { BarUnit } from 'topstepx-api';
const bars = await client.history.retrieveBars({
contractId: string;
live: boolean;
startTime: string; // ISO 8601 format
endTime: string;
unit: BarUnit; // Second, Minute, Hour, Day, Week, Month
unitNumber: number; // e.g., 5 for 5-minute bars
limit: number; // Max bars to return
includePartialBar: boolean;
});
// Returns: Bar[]
interface Bar {
t: string; // timestamp
o: number; // open
h: number; // high
l: number; // low
c: number; // close
v: number; // volume
}
Access via client.marketHub
Subscribe to real-time market data via WebSocket.
// Subscribe to all market data for a contract
await client.marketHub.subscribe('CON.F.US.ENQ.M25');
// Or subscribe selectively
await client.marketHub.subscribeQuotes('CON.F.US.ENQ.M25');
await client.marketHub.subscribeTrades('CON.F.US.ENQ.M25');
await client.marketHub.subscribeDepth('CON.F.US.ENQ.M25');
await client.marketHub.unsubscribe('CON.F.US.ENQ.M25');
// Or unsubscribe selectively
await client.marketHub.unsubscribeQuotes('CON.F.US.ENQ.M25');
await client.marketHub.unsubscribeTrades('CON.F.US.ENQ.M25');
await client.marketHub.unsubscribeDepth('CON.F.US.ENQ.M25');
// Quote updates
client.marketHub.on('quote', ({ contractId, data }) => {
for (const quote of data) {
console.log(`${contractId}: Bid ${quote.bestBid} / Ask ${quote.bestAsk}`);
}
});
// Trade updates
client.marketHub.on('trade', ({ contractId, data }) => {
for (const trade of data) {
console.log(`${contractId}: ${trade.volume} @ ${trade.price}`);
}
});
// Market depth updates
client.marketHub.on('depth', ({ contractId, data }) => {
for (const level of data) {
console.log(`${contractId}: ${level.volume} @ ${level.price}`);
}
});
interface Quote {
symbol: string;
lastPrice: number;
bestBid: number;
bestAsk: number;
change: number;
changePercent: number;
volume: number;
lastUpdated: string;
timestamp: string;
}
interface MarketTrade {
symbolId: string;
price: number;
timestamp: string;
type: 0 | 1; // 0 = Bid, 1 = Ask
volume: number;
}
interface MarketDepth {
price: number;
volume: number;
currentVolume: number;
type: number;
timestamp: string;
}
Access via client.userHub
Subscribe to real-time account updates via WebSocket.
// Subscribe to all account updates
await client.userHub.subscribe(accountId);
// Or subscribe selectively
await client.userHub.subscribeOrders(accountId);
await client.userHub.subscribePositions(accountId);
await client.userHub.subscribeTrades(accountId);
await client.userHub.unsubscribe(accountId);
// Order updates
client.userHub.on('order', (order) => {
console.log(`Order ${order.id}: ${order.status}`);
});
// Position updates
client.userHub.on('position', (position) => {
console.log(`Position: ${position.size} contracts @ ${position.averagePrice}`);
});
// Trade executions
client.userHub.on('trade', (trade) => {
console.log(`Trade: ${trade.size} @ ${trade.price}, P&L: ${trade.profitAndLoss}`);
});
// Account updates
client.userHub.on('account', (account) => {
console.log(`Account ${account.name}: Can trade = ${account.canTrade}`);
});
import {
OrderType,
OrderSide,
OrderStatus,
BarUnit,
PositionType,
TradeType,
} from 'topstepx-api';
// OrderType
OrderType.Limit // 1
OrderType.Market // 2
OrderType.Stop // 3
OrderType.StopLimit // 4
// OrderSide
OrderSide.Buy // 0
OrderSide.Sell // 1
// OrderStatus
OrderStatus.Pending // 0
OrderStatus.Working // 1
OrderStatus.Filled // 2
OrderStatus.Cancelled // 3
OrderStatus.Rejected // 4
OrderStatus.PartiallyFilled // 5
// BarUnit
BarUnit.Second // 1
BarUnit.Minute // 2
BarUnit.Hour // 3
BarUnit.Day // 4
BarUnit.Week // 5
BarUnit.Month // 6
// PositionType
PositionType.Long // 0
PositionType.Short // 1
The library provides typed errors for different failure scenarios:
import {
TopstepXError,
AuthenticationError,
ApiError,
ConnectionError,
} from 'topstepx-api';
try {
await client.connect();
await client.orders.place({ ... });
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Auth failed:', error.message, error.code);
} else if (error instanceof ApiError) {
console.error(`API error on ${error.endpoint}:`, error.message);
} else if (error instanceof ConnectionError) {
console.error('WebSocket error:', error.message);
}
}
All errors extend TopstepXError and include:
message - Error descriptioncode - Error code (if applicable)timestamp - When the error occurredtoJSON() - Serialize for loggingimport {
TopstepXClient,
OrderType,
OrderSide,
BarUnit,
ApiError,
} from 'topstepx-api';
import 'dotenv/config';
async function main() {
const client = new TopstepXClient({
username: process.env.TOPSTEP_USERNAME!,
apiKey: process.env.TOPSTEP_API_KEY!,
});
try {
// Connect
await client.connect();
console.log('Connected to TopstepX');
// Get accounts
const accounts = await client.accounts.search({ onlyActiveAccounts: true });
const account = accounts[0];
console.log(`Using account: ${account.name} (${account.id})`);
// Search for ES contract
const contracts = await client.contracts.search({
searchText: 'ES',
live: false,
});
const esContract = contracts.find(c => c.activeContract);
console.log(`Found contract: ${esContract?.id}`);
// Get historical data
const endTime = new Date();
const startTime = new Date(endTime.getTime() - 24 * 60 * 60 * 1000);
const bars = await client.history.retrieveBars({
contractId: esContract!.id,
live: false,
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
unit: BarUnit.Hour,
unitNumber: 1,
limit: 24,
includePartialBar: false,
});
console.log(`Retrieved ${bars.length} hourly bars`);
// Subscribe to real-time quotes
client.marketHub.on('quote', ({ contractId, data }) => {
const quote = data[0];
console.log(`${contractId}: ${quote.bestBid} / ${quote.bestAsk}`);
});
await client.marketHub.subscribeQuotes(esContract!.id);
// Subscribe to account updates
client.userHub.on('order', (order) => {
console.log(`Order update: ${order.id} - ${order.status}`);
});
await client.userHub.subscribe(account.id);
// Place a limit order
const order = await client.orders.place({
accountId: account.id,
contractId: esContract!.id,
type: OrderType.Limit,
side: OrderSide.Buy,
size: 1,
limitPrice: bars[bars.length - 1].c - 10, // 10 points below last close
});
console.log(`Placed order: ${order.orderId}`);
// Check open orders
const openOrders = await client.orders.searchOpen({ accountId: account.id });
console.log(`Open orders: ${openOrders.length}`);
// Cancel the order
await client.orders.cancel({
accountId: account.id,
orderId: order.orderId,
});
console.log('Order cancelled');
// Keep running for real-time updates
await new Promise(resolve => setTimeout(resolve, 30000));
} catch (error) {
if (error instanceof ApiError) {
console.error(`API Error [${error.endpoint}]: ${error.message}`);
} else {
console.error('Error:', error);
}
} finally {
await client.disconnect();
console.log('Disconnected');
}
}
main();
All types are exported for full TypeScript support:
import type {
// Config
TopstepXClientConfig,
TopstepXClientEvents,
// REST types
Account,
Order,
Position,
Trade,
Contract,
Bar,
// Request types
PlaceOrderRequest,
ModifyOrderRequest,
SearchOrdersRequest,
RetrieveBarsRequest,
// WebSocket types
Quote,
MarketTrade,
MarketDepth,
OrderUpdate,
PositionUpdate,
TradeUpdate,
} from 'topstepx-api';
MIT