@agrigoryan/questrade-api
v0.1.0
Published
TypeScript client for the Questrade API. Zero dependencies.
Readme
questrade-api
TypeScript client for the Questrade REST & WebSocket API. Zero runtime dependencies.
Features
- Full coverage of Questrade's v1 REST API (accounts, markets, orders, symbols)
- Real-time L1 quote and notification streaming via WebSocket
- OAuth2 authentication (refresh-token and auth-code flows)
- Automatic token refresh on 401 via callback
- Automatic rate-limit retry on 429
- Typed responses with string-union enums
- Works with Bun and Node.js (ESM)
Install
bun add questrade-api
# or
npm install questrade-apiQuick Start
import {
exchangeRefreshToken,
ApiClient,
getAccounts,
getPositions,
getQuotes,
} from "questrade-api";
// 1. Authenticate with a refresh token
const creds = await exchangeRefreshToken("your_refresh_token");
// 2. Create a client with automatic token refresh
const client = new ApiClient({
accessToken: creds.accessToken,
apiServer: creds.apiServer,
onTokenExpired: async () => {
const fresh = await exchangeRefreshToken(creds.refreshToken);
creds.accessToken = fresh.accessToken;
creds.refreshToken = fresh.refreshToken;
creds.apiServer = fresh.apiServer;
return { accessToken: fresh.accessToken, apiServer: fresh.apiServer };
},
});
// 3. Use the API
const { accounts } = await getAccounts(client);
const { positions } = await getPositions(client, accounts[0].number);
const { quotes } = await getQuotes(client, [8049]); // AAPLAuthentication
Questrade uses OAuth2. You'll need a refresh token from the Questrade API Hub.
Refresh Token Flow
import { exchangeRefreshToken } from "questrade-api";
const creds = await exchangeRefreshToken("your_refresh_token");
// creds.accessToken, creds.refreshToken, creds.apiServer, creds.expiresInAuthorization Code Flow
import { exchangeAuthCode } from "questrade-api";
const creds = await exchangeAuthCode(code, clientId, redirectUri);Practice (Demo) Account
Pass true as the last argument to use the practice login server:
const creds = await exchangeRefreshToken("your_refresh_token", true);Token Revocation
import { revokeToken } from "questrade-api";
await revokeToken(creds.refreshToken);API Reference
All API functions take an ApiClient as the first argument.
Accounts
import {
getTime,
getAccounts,
getPositions,
getBalances,
getExecutions,
getOrders,
getActivities,
} from "questrade-api";
await getTime(client);
await getAccounts(client);
await getPositions(client, accountId);
await getBalances(client, accountId);
await getExecutions(client, accountId, startTime, endTime);
await getOrders(client, accountId, { stateFilter: "All" });
await getActivities(client, accountId, startTime, endTime);Market Data
import {
getMarkets,
getQuotes,
getOptionQuotes,
getStrategyQuotes,
getCandles,
} from "questrade-api";
await getMarkets(client);
await getQuotes(client, [8049, 9291]);
await getOptionQuotes(client, { optionIdFilter: [...], filters: [...] });
await getStrategyQuotes(client, { variants: [...] });
await getCandles(client, symbolId, startTime, endTime, "OneDay");Symbols
import {
getSymbolsByIds,
getSymbolsByNames,
searchSymbols,
getOptionChain,
} from "questrade-api";
await getSymbolsByIds(client, [8049]);
await getSymbolsByNames(client, ["AAPL", "MSFT"]);
await searchSymbols(client, "AAPL");
await getOptionChain(client, symbolId);Orders
import {
createOrder,
updateOrder,
cancelOrder,
getOrderImpact,
} from "questrade-api";
const order = await createOrder(client, accountId, {
symbolId: 8049,
quantity: 10,
orderType: "Limit",
timeInForce: "GoodTillCanceled",
limitPrice: 150.0,
action: "Buy",
});
await updateOrder(client, accountId, orderId, { limitPrice: 155.0 });
await cancelOrder(client, accountId, orderId);
await getOrderImpact(client, accountId, orderRequest);Streaming
import { streamQuotes, streamNotifications } from "questrade-api";
// L1 quote stream (note: may freeze market data in other IQ platforms)
const quoteStream = await streamQuotes(client, [8049], {
onQuote: (quote) => console.log(quote),
onError: (err) => console.error(err),
onClose: () => console.log("closed"),
});
// Notification stream (order fills, executions)
const notifStream = await streamNotifications(client, {
onNotification: (event) => console.log(event),
onError: (err) => console.error(err),
onClose: () => console.log("closed"),
});
// Clean up
quoteStream.close();
notifStream.close();Error Handling
API errors throw a QuestradeError with structured fields:
import { QuestradeError } from "questrade-api";
try {
await createOrder(client, accountId, orderRequest);
} catch (err) {
if (err instanceof QuestradeError) {
console.log(err.httpStatus); // 400
console.log(err.code); // error code from Questrade
console.log(err.message); // human-readable message
console.log(err.orderId); // present on order errors
}
}Development
bun install
bun run typecheck # type-check only
bun run build # compile to dist/License
MIT
