topstepx
v0.1.0
Published
TypeScript SDK for the TopstepX trading API — REST, WebSocket, and React hooks
Downloads
79
Maintainers
Readme
topstepx
TypeScript SDK for the TopstepX trading API -- REST, WebSocket, and React hooks.
Install
npm install topstepxQuick Start
Node.js / Bots
import { createClient } from 'topstepx';
// Create client with username and API key (Supabase-style)
const client = createClient('your-username', 'your-api-key');
// Get accounts
const accounts = await client.accounts.search({});
// Place a market order
const order = await client.orders.place({
accountId: accounts[0].id,
contractId: 'CON.F.US.EP.U25', // E-mini S&P 500
type: 2, // Market
side: 0, // Buy
size: 1,
});Or use the class-based approach:
import { TopstepXClient } from 'topstepx';
const client = new TopstepXClient({
credentials: { userName: 'your-username', apiKey: 'your-api-key' },
});React
import { TopstepXProvider, useAccounts, usePositions } from 'topstepx/react';
function App() {
return (
<TopstepXProvider userName="your-username" apiKey="your-api-key">
<Dashboard />
</TopstepXProvider>
);
}
function Dashboard() {
const { data: accounts, isLoading } = useAccounts();
const { data: positions } = usePositions(accounts?.[0]?.id);
if (isLoading) return <div>Loading...</div>;
return (
<div>
<h1>Positions for {accounts?.[0]?.name}</h1>
<ul>
{positions?.map(p => (
<li key={p.contractId}>
{p.contractId}: {p.size} contracts
</li>
))}
</ul>
</div>
);
}Features
- Full TypeScript support - Complete type definitions for all API entities, enums, and responses
- REST Client - Type-safe wrappers for all endpoints: accounts, contracts, orders, positions, trades, history bars
- Real-time WebSocket - SignalR-based live data: orders, positions, trades, quotes, market depth
- React Hooks - Built-in hooks with loading/error states and automatic real-time data synchronization
- Fluent Order Builder - Chainable API for building complex orders with validation
- Advanced Order Types - OCO (One-Cancels-Other), multi-target brackets, trailing stops, ATM strategies
- Trade Copier - Mirror trades from one account to multiple target accounts
- Automatic Token Refresh - Handles authentication and token renewal automatically
- Rate Limiting - Built-in request queuing and retry logic
- ESM-only - Tree-shakeable, zero unnecessary dependencies
Table of Contents
- Connection URLs
- Rate Limits
- Authentication
- Configuration
- REST API
- Real-time WebSocket
- Order Builder
- Advanced Features
- React Hooks
- Error Handling
- TypeScript
- Enums
Connection URLs
| Service | URL |
|---------|-----|
| REST API | https://api.topstepx.com |
| User Hub | wss://rtc.topstepx.com/hubs/user |
| Market Hub | wss://rtc.topstepx.com/hubs/market |
Rate Limits
The Gateway API employs rate limiting to ensure stability and reliability:
| Endpoint | Limit |
|----------|-------|
| POST /api/History/retrieveBars | 50 requests / 30 seconds |
| All other endpoints | 200 requests / 60 seconds |
If you exceed the rate limit, the API returns an HTTP 429 (Too Many Requests) error. The SDK includes built-in retry logic.
Authentication
The SDK supports two authentication methods via JSON Web Tokens (JWT).
API Key Authentication (Recommended)
The SDK handles token acquisition automatically. Tokens are valid for 24 hours and are automatically refreshed.
const client = createClient('username', 'apiKey');API Endpoint: POST https://api.topstepx.com/api/Auth/loginKey
Request:
{
"userName": "your-username",
"apiKey": "your-api-key"
}Response:
{
"token": "your_session_token_here",
"success": true,
"errorCode": 0,
"errorMessage": null
}Application Credentials (Legacy)
For authorized applications with admin credentials:
const client = new TopstepXClient({
credentials: {
userName: 'username',
password: 'password',
deviceId: 'device-id',
appId: 'app-id',
verifyKey: 'verify-key',
},
});API Endpoint: POST https://api.topstepx.com/api/Auth/loginApp
Token Validation
The SDK automatically validates and refreshes tokens. To manually validate:
API Endpoint: POST https://api.topstepx.com/api/Auth/validate
Response:
{
"success": true,
"errorCode": 0,
"errorMessage": null,
"newToken": "NEW_TOKEN"
}Custom Token Store
Implement the TokenStore interface for custom token persistence (Redis, database, etc.):
import { createClient, type TokenStore } from 'topstepx';
const redisTokenStore: TokenStore = {
async get() {
return await redis.get('topstepx:token');
},
async set(token) {
await redis.set('topstepx:token', token, 'EX', 3600);
},
async clear() {
await redis.del('topstepx:token');
},
};
const client = createClient('username', 'apiKey', {
tokenStore: redisTokenStore,
});Configuration
Client Options
import { createClient } from 'topstepx';
const client = createClient('username', 'apiKey', {
baseUrl: 'https://api.topstepx.com', // Optional: custom REST API URL
rtcUrl: 'https://rtc.topstepx.com', // Optional: custom WebSocket URL
tokenStore: customTokenStore, // Optional: custom token persistence
});Environment Variables
# .env
TOPSTEPX_USERNAME=your-username
TOPSTEPX_API_KEY=your-api-keyimport { createClient } from 'topstepx';
const client = createClient(
process.env.TOPSTEPX_USERNAME!,
process.env.TOPSTEPX_API_KEY!
);REST API
All REST API methods are available through the client object.
Accounts
Search for accounts associated with your user.
API Endpoint: POST https://api.topstepx.com/api/Account/search
// List all accounts
const { accounts } = await client.accounts.search({});
// Get only active trading accounts
const { accounts } = await client.accounts.search({ onlyActiveAccounts: true });
// Get only active accounts (canTrade = true)
const activeAccounts = await client.getActiveAccounts();
// Get account summary (account + positions + orders)
const summary = await client.getAccountSummary(accountId);Response:
{
"accounts": [
{
"id": 1,
"name": "TEST_ACCOUNT_1",
"balance": 50000,
"canTrade": true,
"isVisible": true
}
],
"success": true,
"errorCode": 0,
"errorMessage": null
}Contracts
Contract ID Format: CON.F.US.{SYMBOL}.{MONTH}{YEAR}
Examples:
CON.F.US.EP.U25- E-mini S&P 500, September 2025CON.F.US.ENQ.U25- E-mini NASDAQ-100, September 2025CON.F.US.BP6.U25- British Pound, September 2025
API Endpoints:
- Search:
POST https://api.topstepx.com/api/Contract/search - By ID:
POST https://api.topstepx.com/api/Contract/searchById - Available:
POST https://api.topstepx.com/api/Contract/available
// Search contracts (returns up to 20 contracts)
const { contracts } = await client.contracts.search({
searchText: 'NQ',
live: false,
});
// Get contract by ID
const { contract } = await client.contracts.searchById({
contractId: 'CON.F.US.ENQ.H25'
});
// Get active contracts (with caching)
const activeContracts = await client.getActiveContracts();
// Find contract by symbol (uses cache)
const contract = await client.findContract('ESM4');Contract Response:
{
"id": "CON.F.US.ENQ.U25",
"name": "NQU5",
"description": "E-mini NASDAQ-100: September 2025",
"tickSize": 0.25,
"tickValue": 5,
"activeContract": true,
"symbolId": "F.US.ENQ"
}Orders
API Endpoints:
- Place:
POST https://api.topstepx.com/api/Order/place - Search:
POST https://api.topstepx.com/api/Order/search - Search Open:
POST https://api.topstepx.com/api/Order/searchOpen - Cancel:
POST https://api.topstepx.com/api/Order/cancel - Modify:
POST https://api.topstepx.com/api/Order/modify
import { OrderType, OrderSide } from 'topstepx';
// Place a market order
const { orderId } = await client.orders.place({
accountId: 123,
contractId: 'CON.F.US.EP.U25',
type: OrderType.Market, // 2
side: OrderSide.Bid, // 0 = Buy
size: 1,
});
// Place a limit order
const { orderId } = await client.orders.place({
accountId: 123,
contractId: 'CON.F.US.EP.U25',
type: OrderType.Limit, // 1
side: OrderSide.Bid, // 0 = Buy
size: 1,
limitPrice: 5000.00,
});
// Place a stop order
const { orderId } = await client.orders.place({
accountId: 123,
contractId: 'CON.F.US.EP.U25',
type: OrderType.Stop, // 4
side: OrderSide.Ask, // 1 = Sell
size: 1,
stopPrice: 4900.00,
});
// Place order with brackets (stop loss + take profit)
const { orderId } = await client.orders.place({
accountId: 123,
contractId: 'CON.F.US.EP.U25',
type: OrderType.Market,
side: OrderSide.Bid,
size: 1,
stopLossBracket: {
ticks: 20,
type: OrderType.Stop,
},
takeProfitBracket: {
ticks: 40,
type: OrderType.Limit,
},
});
// Search orders by time range
const { orders } = await client.orders.search({
accountId: 123,
startTimestamp: '2025-07-18T21:00:00Z',
endTimestamp: '2025-07-18T22:00:00Z',
});
// Get open orders
const { orders } = await client.orders.searchOpen({ accountId: 123 });
// Cancel an order
await client.orders.cancel({ accountId: 123, orderId: 789 });
// Cancel all orders
await client.cancelAllOrders(123);
// Modify an order
await client.orders.modify({
accountId: 123,
orderId: 789,
size: 2,
limitPrice: 5100.00,
});Order Response:
{
"id": 36598,
"accountId": 704,
"contractId": "CON.F.US.EP.U25",
"symbolId": "F.US.EP",
"creationTimestamp": "2025-07-18T21:00:01.268009+00:00",
"updateTimestamp": "2025-07-18T21:00:01.268009+00:00",
"status": 2,
"type": 2,
"side": 0,
"size": 1,
"limitPrice": null,
"stopPrice": null,
"fillVolume": 1,
"filledPrice": 6335.25,
"customTag": null
}Positions
API Endpoints:
- Search Open:
POST https://api.topstepx.com/api/Position/searchOpen - Close:
POST https://api.topstepx.com/api/Position/closeContract - Partial Close:
POST https://api.topstepx.com/api/Position/partialCloseContract
// Get open positions
const { positions } = await client.positions.searchOpen({ accountId: 123 });
// Close a position
await client.positions.closeContract({
accountId: 123,
contractId: 'CON.F.US.EP.U25',
});
// Partial close
await client.positions.partialCloseContract({
accountId: 123,
contractId: 'CON.F.US.EP.U25',
size: 1,
});
// Flatten all positions
await client.flattenAll(123);Position Response:
{
"positions": [
{
"id": 6124,
"accountId": 536,
"contractId": "CON.F.US.GMET.J25",
"creationTimestamp": "2025-04-21T19:52:32.175721+00:00",
"type": 1,
"size": 2,
"averagePrice": 1575.75
}
],
"success": true,
"errorCode": 0,
"errorMessage": null
}Trades
API Endpoint: POST https://api.topstepx.com/api/Trade/search
// Search trades
const { trades } = await client.trades.search({
accountId: 123,
startTimestamp: '2025-01-20T15:47:39.882Z',
endTimestamp: '2025-01-30T15:47:39.882Z',
});Trade Response:
{
"trades": [
{
"id": 8604,
"accountId": 203,
"contractId": "CON.F.US.EP.H25",
"creationTimestamp": "2025-01-21T16:13:52.523293+00:00",
"price": 6065.25,
"profitAndLoss": 50.00,
"fees": 1.40,
"side": 1,
"size": 1,
"voided": false,
"orderId": 14328
}
],
"success": true,
"errorCode": 0,
"errorMessage": null
}Note: A null value for profitAndLoss indicates a half-turn trade.
History
API Endpoint: POST https://api.topstepx.com/api/History/retrieveBars
Limits: Maximum 20,000 bars per request
import { BarUnit } from 'topstepx';
// Get historical bars
const { bars } = await client.history.retrieveBars({
contractId: 'CON.F.US.RTY.Z24',
live: false,
startTime: '2024-12-01T00:00:00Z',
endTime: '2024-12-31T21:00:00Z',
unit: BarUnit.Hour, // 3
unitNumber: 1,
limit: 1000,
includePartialBar: false,
});
// Convenient helper with defaults
const bars = await client.getBars('CON.F.US.EP.U25', {
unit: BarUnit.Minute,
unitNumber: 5,
limit: 500,
});Bar Response:
{
"bars": [
{
"t": "2024-12-20T14:00:00+00:00",
"o": 2208.10,
"h": 2217.00,
"l": 2206.70,
"c": 2210.10,
"v": 87
}
],
"success": true,
"errorCode": 0,
"errorMessage": null
}Bar Fields:
t- Timestampo- Open priceh- High pricel- Low pricec- Close pricev- Volume
Real-time WebSocket
Connect to real-time data streams via SignalR hubs. The SDK uses WebSockets with automatic reconnection.
Connection Management
// Connect all hubs
await client.connect();
// Check connection state
import { HubConnectionState } from '@microsoft/signalr';
const isConnected = client.userHub.state === HubConnectionState.Connected;
// Disconnect
await client.disconnect();User Hub
Real-time account, order, position, and trade events.
WebSocket URL: wss://rtc.topstepx.com/hubs/user
// Subscribe to order updates
const unsubOrders = client.userHub.onOrder((order) => {
console.log('Order update:', order);
});
// Subscribe to position updates
const unsubPositions = client.userHub.onPosition((position) => {
console.log('Position update:', position);
});
// Subscribe to trade events
const unsubTrades = client.userHub.onTrade((trade) => {
console.log('New trade:', trade);
});
// Subscribe to account updates
const unsubAccounts = client.userHub.onAccount((account) => {
console.log('Account update:', account);
});
// Start receiving updates for an account
await client.userHub.subscribeOrders(accountId);
await client.userHub.subscribePositions(accountId);
await client.userHub.subscribeTrades(accountId);
await client.userHub.subscribeAccounts();
// Cleanup
unsubOrders();
unsubPositions();
unsubTrades();
unsubAccounts();GatewayUserOrder Event
{
"id": 789,
"accountId": 123,
"contractId": "CON.F.US.EP.U25",
"symbolId": "F.US.EP",
"creationTimestamp": "2024-07-21T13:45:00Z",
"updateTimestamp": "2024-07-21T13:46:00Z",
"status": 1,
"type": 1,
"side": 0,
"size": 1,
"limitPrice": 2100.50,
"stopPrice": null,
"fillVolume": 0,
"filledPrice": null,
"customTag": "strategy-1"
}Fields:
id- Order IDaccountId- Account IDcontractId- Contract IDsymbolId- Symbol IDcreationTimestamp- When order was createdupdateTimestamp- When order was last updatedstatus- Order status (see OrderStatus enum)type- Order type (see OrderType enum)side- Order side (0=Bid, 1=Ask)size- Order sizelimitPrice- Limit price (if applicable)stopPrice- Stop price (if applicable)fillVolume- Number of contracts filledfilledPrice- Price at which order was filledcustomTag- Custom tag for the order
GatewayUserPosition Event
{
"id": 456,
"accountId": 123,
"contractId": "CON.F.US.EP.U25",
"creationTimestamp": "2024-07-21T13:45:00Z",
"type": 1,
"size": 2,
"averagePrice": 2100.25
}Fields:
id- Position IDaccountId- Account IDcontractId- Contract IDcreationTimestamp- When position was openedtype- Position type (1=Long, 2=Short)size- Position sizeaveragePrice- Average entry price
GatewayUserTrade Event
{
"id": 101112,
"accountId": 123,
"contractId": "CON.F.US.EP.U25",
"creationTimestamp": "2024-07-21T13:47:00Z",
"price": 2100.75,
"profitAndLoss": 50.25,
"fees": 2.50,
"side": 0,
"size": 1,
"voided": false,
"orderId": 789
}GatewayUserAccount Event
{
"id": 123,
"name": "Main Trading Account",
"balance": 10000.50,
"canTrade": true,
"isVisible": true,
"simulated": false
}Market Hub
Real-time market data: quotes and depth.
WebSocket URL: wss://rtc.topstepx.com/hubs/market
// Subscribe to quotes
const unsubQuotes = client.marketHub.onQuote((contractId, quote) => {
console.log('Quote:', contractId, quote.lastPrice);
});
// Subscribe to market depth
const unsubDepth = client.marketHub.onDepth((contractId, depth) => {
console.log('Depth:', contractId, depth);
});
// Subscribe to market trades
const unsubTrades = client.marketHub.onTrade((contractId, trade) => {
console.log('Trade:', contractId, trade);
});
// Start receiving data for a contract
await client.marketHub.subscribeQuotes(contractId);
await client.marketHub.subscribeDepth(contractId);
await client.marketHub.subscribeTrades(contractId);GatewayQuote Event
{
"symbol": "F.US.EP",
"symbolName": "/ES",
"lastPrice": 2100.25,
"bestBid": 2100.00,
"bestAsk": 2100.50,
"change": 25.50,
"changePercent": 0.14,
"open": 2090.00,
"high": 2110.00,
"low": 2080.00,
"volume": 12000,
"lastUpdated": "2024-07-21T13:45:00Z",
"timestamp": "2024-07-21T13:45:00Z"
}Fields:
symbol- Symbol IDsymbolName- Friendly symbol namelastPrice- Last traded pricebestBid- Current best bidbestAsk- Current best askchange- Price change since previous closechangePercent- Percent changeopen- Opening pricehigh- Session highlow- Session lowvolume- Total traded volumelastUpdated- Last update timetimestamp- Quote timestamp
GatewayDepth Event
{
"timestamp": "2024-07-21T13:45:00Z",
"type": 1,
"price": 2100.00,
"volume": 10,
"currentVolume": 5
}Fields:
timestamp- DOM update timestamptype- DOM type (see DomType enum)price- Price levelvolume- Total volume at this levelcurrentVolume- Current volume at this level
GatewayTrade Event
{
"symbolId": "F.US.EP",
"price": 2100.25,
"timestamp": "2024-07-21T13:45:00Z",
"type": 0,
"volume": 2
}Convenient Subscriptions
// Subscribe to ticker with cleanup
const unsubscribe = await client.subscribeTicker(contractId, (quote) => {
console.log('Quote:', quote);
});
// Later...
await unsubscribe();
// Get latest quote (one-shot)
const quote = await client.getLatestQuote(contractId, 5000); // 5 second timeoutEvent Monitoring
// Watch for fills
const unsubFill = client.onFill(accountId, (order) => {
console.log('Order filled:', order);
});
// Wait for a specific order to fill
try {
const filledOrder = await client.waitForFill(orderId, 30000); // 30 second timeout
console.log('Order filled:', filledOrder);
} catch (error) {
console.log('Timeout waiting for fill');
}
// Watch a specific position
const unsubPosition = client.watchPosition(accountId, contractId, (position) => {
console.log('Position update:', position);
});
// Watch all orders for an account
const unsubOrders = client.watchOrders(accountId, (order) => {
console.log('Order update:', order);
});Order Builder
Build orders fluently with validation.
// Simple market order
const orderId = await client
.order(accountId)
.market()
.buy(1)
.onContract(contractId)
.place();
// Limit order
const orderId = await client
.order(accountId)
.limit(5000.00)
.sell(2)
.onContract(contractId)
.withTag('my-strategy')
.place();
// Stop order
const orderId = await client
.order(accountId)
.stop(4900.00)
.buy(1)
.onContract(contractId)
.place();
// Trailing stop
const orderId = await client
.order(accountId)
.trailingStop(10) // 10 ticks
.sell(1)
.onContract(contractId)
.place();
// Modify trailing distance
await client.modifyTrailingDistance(accountId, orderId, 15); // 15 ticksAdvanced Features
OCO Orders
One-Cancels-Other: Two orders where filling one automatically cancels the other.
import { OrderType, OrderSide } from 'topstepx';
const oco = await client.placeOCO({
accountId: 123,
contractId: 'CON.F.US.EP.U25',
orderA: {
type: OrderType.Limit,
side: OrderSide.Ask,
size: 1,
limitPrice: 5100.00, // Take profit
},
orderB: {
type: OrderType.Stop,
side: OrderSide.Ask,
size: 1,
stopPrice: 4900.00, // Stop loss
},
timeoutMs: 60000, // Optional: cancel both after 60 seconds
});
console.log('OCO placed:', oco.orderIdA, oco.orderIdB);
// Wait for one to fill
const result = await oco.result;
console.log('Filled:', result.filledOrderId);
console.log('Cancelled:', result.cancelledOrderId);
console.log('Both filled?', result.bothFilled);
// Manual cancellation
await oco.cancel();Bracket Orders
Entry order with stop-loss and take-profit brackets.
// Place bracket order
const orderId = await client.placeBracketOrder({
accountId: 123,
contractId: 'CON.F.US.EP.U25',
type: OrderType.Market,
side: OrderSide.Bid,
size: 1,
stopLossTicks: 20,
takeProfitTicks: 40,
});
// Add brackets to existing position
const bracketResult = await client.addBracketToPosition({
accountId: 123,
contractId: 'CON.F.US.EP.U25',
stopLossTicks: 20,
takeProfitTicks: 40,
});Multi-Target Brackets
Entry with multiple take-profit levels.
const result = await client.placeMultiTargetBracket({
accountId: 123,
contractId: 'CON.F.US.EP.U25',
side: OrderSide.Bid,
entryType: OrderType.Market,
entrySize: 3,
targets: [
{ ticks: 20, size: 1 }, // Close 1 contract at +20 ticks
{ ticks: 40, size: 1 }, // Close 1 contract at +40 ticks
{ ticks: 60, size: 1 }, // Close 1 contract at +60 ticks
],
stopLossTicks: 20,
});
// Wait for entry fill
const entryFill = await result.result;
console.log('Entry filled at:', entryFill.filledPrice);
// Cancel all orders
await result.cancel();ATM Strategies
At-the-money strategy templates with breakeven logic.
const atmResult = await client.executeATMStrategy(
123, // accountId
'CON.F.US.EP.U25', // contractId
OrderSide.Bid, // side
OrderType.Market, // entryType
undefined, // limitPrice
undefined, // stopPrice
{
name: 'my-strategy',
targets: [
{ ticks: 20, size: 1 },
{ ticks: 40, size: 1 },
],
stopLoss: {
ticks: 20,
type: OrderType.Stop,
},
breakeven: {
triggerTicks: 15, // Move to breakeven at +15 ticks profit
offsetTicks: 2, // Lock in 2 ticks profit
},
}
);
// Cancel strategy
await atmResult.cancel();Trade Copier
Copy trades from one account to multiple targets.
// Copy a single trade
await client.copyTrade(
sourceAccountId,
[targetAccountId1, targetAccountId2],
{
contractId: 'CON.F.US.EP.U25',
side: OrderSide.Bid,
size: 2,
},
{
sizeRatio: 0.5, // Copy at 50% size
maxSize: 5,
tagPrefix: 'copied',
}
);
// Continuous position mirroring
const mirror = await client.mirrorPositions(
sourceAccountId,
[targetAccountId1, targetAccountId2],
{
sizeRatio: 1.0,
maxSize: 10,
tagPrefix: 'mirror',
ignoreExistingPositions: false, // Sync existing positions on start
}
);
// Check if mirroring is active
console.log('Active:', mirror.active);
// Stop mirroring
await mirror.stop();React Hooks
Provider
import { TopstepXProvider } from 'topstepx/react';
function App() {
return (
<TopstepXProvider userName="user" apiKey="key">
<TradingDashboard />
</TopstepXProvider>
);
}Account Hooks
import { useAccounts } from 'topstepx/react';
function AccountSelector() {
const { data: accounts, isLoading, error } = useAccounts();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<select>
{accounts?.map(account => (
<option key={account.id} value={account.id}>
{account.name}
</option>
))}
</select>
);
}Position Hooks
import { usePositions } from 'topstepx/react';
function PositionList({ accountId }: { accountId: number }) {
const { data: positions, isLoading } = usePositions(accountId);
return (
<ul>
{positions?.map(position => (
<li key={position.contractId}>
{position.contractId}: {position.size} @ {position.averagePrice}
</li>
))}
</ul>
);
}Order Hooks
import { useOrders, useOpenOrders } from 'topstepx/react';
function OrderBook({ accountId }: { accountId: number }) {
// All orders (REST only)
const { data: allOrders } = useOrders({ accountId });
// Open orders with real-time updates
const { data: openOrders } = useOpenOrders(accountId);
return (
<div>
<h2>Open Orders ({openOrders?.length ?? 0})</h2>
{/* Render orders */}
</div>
);
}Trade Hooks
import { useTrades } from 'topstepx/react';
function TradeHistory({ accountId }: { accountId: number }) {
const { data: trades, isLoading } = useTrades(accountId);
return (
<table>
<tbody>
{trades?.map(trade => (
<tr key={trade.id}>
<td>{trade.timestamp}</td>
<td>{trade.side}</td>
<td>{trade.size}</td>
<td>{trade.price}</td>
</tr>
))}
</tbody>
</table>
);
}Market Data Hooks
import { useBars, useContracts } from 'topstepx/react';
function PriceChart({ contractId }: { contractId: string }) {
const { data: bars } = useBars(contractId, {
unit: BarUnit.Minute,
unitNumber: 5,
limit: 100,
});
return <Chart data={bars} />;
}
function ContractSearch() {
const { data: contracts } = useContracts({ searchText: 'ES' });
return <ContractList contracts={contracts} />;
}Hub Hooks
import { useUserHub, useMarketHub } from 'topstepx/react';
function RealTimeMonitor({ accountId }: { accountId: number }) {
const userHub = useUserHub();
const marketHub = useMarketHub();
// Subscribe to updates
useEffect(() => {
if (userHub.isConnected) {
userHub.subscribeOrders(accountId);
userHub.subscribePositions(accountId);
}
}, [userHub.isConnected, accountId]);
return (
<div>
<div>User Hub: {userHub.state}</div>
<div>Market Hub: {marketHub.state}</div>
</div>
);
}Hook Types
All hooks return a QueryResult<T>:
interface QueryResult<T> {
data: T | undefined;
isLoading: boolean;
error: Error | undefined;
refetch: () => void;
}Error Handling
The SDK throws typed errors for different failure scenarios:
import { ApiError, AuthError, RateLimitError, ConnectionError } from 'topstepx';
try {
await client.orders.place({ ... });
} catch (error) {
if (error instanceof AuthError) {
console.log('Authentication failed:', error.message);
// Token expired or invalid credentials
} else if (error instanceof RateLimitError) {
console.log('Rate limited, retry after:', error.retryAfter);
// Too many requests
} else if (error instanceof ApiError) {
console.log('API error:', error.statusCode, error.message);
// API returned error response
} else if (error instanceof ConnectionError) {
console.log('Connection error:', error.message);
// WebSocket connection issues
}
}TypeScript
The SDK is written in TypeScript and provides full type definitions.
Core Types
import type {
Account,
Contract,
Position,
Order,
Trade,
Bar,
GatewayQuote,
GatewayDepth,
} from 'topstepx';Event Types
import type {
GatewayUserOrder,
GatewayUserPosition,
GatewayUserTrade,
GatewayUserAccount,
} from 'topstepx';Enums
OrderType
| Enum | Value | Description |
|------|-------|-------------|
| OrderType.Unknown | 0 | Unknown order type |
| OrderType.Limit | 1 | Limit order |
| OrderType.Market | 2 | Market order |
| OrderType.StopLimit | 3 | Stop-limit order |
| OrderType.Stop | 4 | Stop order |
| OrderType.TrailingStop | 5 | Trailing stop order |
| OrderType.JoinBid | 6 | Join bid order |
| OrderType.JoinAsk | 7 | Join ask order |
OrderSide
| Enum | Value | Description |
|------|-------|-------------|
| OrderSide.Bid | 0 | Buy |
| OrderSide.Ask | 1 | Sell |
OrderStatus
| Enum | Value | Description |
|------|-------|-------------|
| OrderStatus.None | 0 | No status |
| OrderStatus.Open | 1 | Order is open/working |
| OrderStatus.Filled | 2 | Order has been filled |
| OrderStatus.Cancelled | 3 | Order has been cancelled |
| OrderStatus.Expired | 4 | Order has expired |
| OrderStatus.Rejected | 5 | Order was rejected |
| OrderStatus.Pending | 6 | Order is pending |
PositionType
| Enum | Value | Description |
|------|-------|-------------|
| PositionType.Undefined | 0 | Undefined position type |
| PositionType.Long | 1 | Long position |
| PositionType.Short | 2 | Short position |
BarUnit
| Enum | Value | Description |
|------|-------|-------------|
| BarUnit.Second | 1 | Seconds |
| BarUnit.Minute | 2 | Minutes |
| BarUnit.Hour | 3 | Hours |
| BarUnit.Day | 4 | Days |
| BarUnit.Week | 5 | Weeks |
| BarUnit.Month | 6 | Months |
TradeLogType
| Enum | Value | Description |
|------|-------|-------------|
| TradeLogType.Buy | 0 | Buy trade |
| TradeLogType.Sell | 1 | Sell trade |
DomType
| Enum | Value | Description |
|------|-------|-------------|
| DomType.Unknown | 0 | Unknown DOM type |
| DomType.Ask | 1 | Ask level |
| DomType.Bid | 2 | Bid level |
| DomType.BestAsk | 3 | Best ask |
| DomType.BestBid | 4 | Best bid |
| DomType.Trade | 5 | Trade |
| DomType.Reset | 6 | Reset |
| DomType.Low | 7 | Low |
| DomType.High | 8 | High |
| DomType.NewBestBid | 9 | New best bid |
| DomType.NewBestAsk | 10 | New best ask |
| DomType.Fill | 11 | Fill |
Subpath Exports
The package provides two entry points:
topstepx-- Core SDK. REST client, WebSocket hubs, order builder, types. Works in any Node.js 18+ environment with no additional dependencies.topstepx/react-- React hooks and provider. Wraps the core SDK withuseAccounts,usePositions,useOrders,useTrades, and more. Requires React 18+ or 19+ as a peer dependency.
React is an optional peer dependency. If you only use topstepx (not topstepx/react), you do not need React installed.
Requirements
- Node.js >= 18
- React 18+ or 19+ (optional, for hooks only)
License
MIT
