Client
The Market Data JavaScript Client handles API requests, response parsing, rate-limit tracking, retries, and logging. The SDK supports stocks, options, funds, markets, and utility endpoints.
Get Started Quickly with the MarketDataClient
- Review the documentation on authentication to learn how to set your API token.
- Create a
MarketDataClientinstance and use it to make requests to the Market Data API. - Make a test request and review the console output. The SDK includes logging capabilities to help you debug requests.
- Check the rate limit in the client to track credit usage and how many API credits remain.
- Configure Settings to customize output format, date format, and other universal parameters.
MarketDataClient
class MarketDataClient {
constructor(config?: MarketDataConfig);
readonly ready: Promise<void>;
}
interface MarketDataConfig {
token?: string;
baseUrl?: string;
apiVersion?: string;
maxRetries?: number;
retryInitialWait?: number;
retryMaxWait?: number;
retryFactor?: number;
skipStartupValidation?: boolean;
debug?: boolean;
logger?: Logger;
}
MarketDataClient is the main client class for interacting with the Market Data API. It provides access to all resources (stocks, options, funds, markets, utilities) and handles authentication, rate limiting, and request management.
Properties
token(string, optional): The authentication token for API requests. See authentication documentation for details.rateLimits(UserRateLimits, optional): Current rate limit information. Populated by the startup/user/call (seereadybelow) or by the first successful API request when startup validation is skipped.ready(Promise<void>): Resolves when the eager/user/validation completes. Rejects withAuthenticationErroron bad tokens; transient errors are logged and swallowed. Await it if you need the fail-fast behaviour on construction.baseUrl(string): The base URL for API requests (default:https://api.marketdata.app).apiVersion(string): The API version to use (default:v1).headers(Record<string, string>): HTTP headers includingAuthorizationandUser-Agent.logger(Logger): The logger instance used for diagnostic output.settings(MarketDataSettings): Resolved configuration including env-var defaults. See Settings for details.
Resources
stocks(StocksResource): Access to stocks endpoints (prices, quotes, candles, earnings, news)options(OptionsResource): Access to options endpoints (chain, expirations, quotes, lookup)funds(FundsResource): Access to funds endpoints (candles)markets(MarketsResource): Access to markets endpoints (status)utilities(UtilitiesResource): Access to utility endpoints (status,headers)
constructor
new MarketDataClient(config?: MarketDataConfig)
Creates and configures a new MarketDataClient instance. This initializes the client with the provided token (or reads it from the MARKETDATA_TOKEN environment variable), sets up HTTP headers, prepares the resource namespaces, and kicks off eager /user/ validation (unless skipStartupValidation: true).
Parameters
-
config(MarketDataConfig, optional)Configuration object. All properties are optional:
token(string): The authentication token. Falls back toMARKETDATA_TOKENenvironment variable if not provided.baseUrl(string): Override the API base URL. Defaults tohttps://api.marketdata.app.apiVersion(string): Override the API version. Defaults tov1.maxRetries(number): Maximum retry attempts for retriable errors. Defaults to3.retryInitialWait(number): Initial wait in seconds before the first retry. Defaults to0.5.retryMaxWait(number): Maximum wait in seconds between retries. Defaults to10.retryFactor(number): Exponential backoff factor. Defaults to2.skipStartupValidation(boolean): Iftrue, skips the eager/user/call the constructor makes to validate the token. Defaults tofalse. Use on serverless platforms where cold-start latency matters.debug(boolean): Iftrue, sets the default logger level toDEBUG. Defaults tofalse.logger(Logger): A custom logger instance. If omitted, the SDK uses its built-inDefaultLogger.
Returns
-
MarketDataClientA new
MarketDataClientinstance ready to make API requests.
Notes
- The client sets a
User-Agentheader of the formmarketdata-sdk-js/{version}(e.g.marketdata-sdk-js/1.0.0). - All authenticated requests include an
Authorization: Bearer {token}header. - The client reuses a single underlying
fetchclient, which benefits from Node's global connection pooling, and enforces a global 50-request concurrency pool across every endpoint. - Every request has a 99-second timeout; a timed-out fetch rejects with
NetworkError. - Configuration properties can also be provided via environment variables — see Settings for the full list and their resolution order.
Example
import { MarketDataClient } from "@marketdata/sdk";
// Token will be read from MARKETDATA_TOKEN environment variable
const client = new MarketDataClient();
// Or provide the token explicitly
const clientWithToken = new MarketDataClient({ token: "your_token_here" });
// Fail fast on invalid tokens (default) — await readiness:
await client.ready;
// Skip the startup /user/ call (e.g. on Lambda cold starts)
const fast = new MarketDataClient({
token: "your_token_here",
skipStartupValidation: true,
});
// Enable debug logging for troubleshooting
const debugClient = new MarketDataClient({ debug: true });
// Provide a custom logger
import { DefaultLogger, LogLevel } from "@marketdata/sdk";
const logger = new DefaultLogger(LogLevel.WARN);
const quietClient = new MarketDataClient({ logger });
Error Handling
Resource methods return a MarketDataPromise<T> that resolves with the data on success and rejects with a subclass of MarketDataClientError on failure. Use standard JavaScript error-handling idioms.
- try / catch
- .catch()
- Asserting rejections in tests
The idiomatic approach.
import {
MarketDataClient,
AuthenticationError,
RateLimitError,
NotFoundError,
} from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const prices = await client.stocks.prices("AAPL");
console.log("Success:", prices);
} catch (error) {
if (error instanceof AuthenticationError) {
console.error("Bad token:", error.message);
} else if (error instanceof RateLimitError) {
console.error("Rate limit exceeded");
} else {
console.error("Failed:", error);
}
}
Fluent chaining without try/catch.
const prices = await client.stocks
.prices("AAPL")
.catch((error) => {
console.error("Failed:", error.message);
return [];
});
import { AuthenticationError } from "@marketdata/sdk";
await expect(client.stocks.prices("AAPL"))
.rejects.toBeInstanceOf(AuthenticationError);
Every thrown error is a subclass of MarketDataClientError and carries HTTP context for support tickets:
try {
await client.stocks.prices("$$$");
} catch (err) {
// Base class — use `instanceof` to narrow to specific errors.
console.log(err.status_code); // 400
console.log(err.request_id); // cf-ray header, e.g. "8a1b2c-SJC"
console.log(err.request_url); // full URL that failed
console.log(err.timestamp); // Date of the request
console.log(err.support_info); // multi-line string to paste into tickets
}
Error classes
| Class | When |
|---|---|
AuthenticationError | 401 — token missing, invalid, or expired |
BadRequestError | 400 — malformed request or invalid parameters |
NotFoundError | 404 — exported but not thrown by default. The SDK translates 404 into an empty response with no_data: true; see no-data handling. The one exception is /user/, which opts into the throw path. |
PaymentRequiredError | 402 — request denied by your plan (data older than your plan allows, premium endpoint on Free/Trial, or mode=cached on Free/Trial) |
ForbiddenError | 403 — access denied. Typically the multi-IP block: the account is temporarily locked when used from more than one IP. Wait ~5 minutes and retry. |
RateLimitError | 429 — per-minute/day rate limit exceeded |
ServerError | 5xx — retriable, server-side failure |
NetworkError | Transport failure: DNS, connection, TLS, or 99s timeout |
ParseError | Response body failed schema or JSON parsing |
ValidationError | Client-side input validation failure (before the network call) |
No-data responses
When the server responds with 404 the SDK resolves the Promise with an empty array (or empty Blob for CSV) and flags the returned MarketDataPromise as no_data: true. Use hasData() to branch on it:
const pending = client.stocks.prices("UNKNOWN");
const prices = await pending;
if (!(await pending.hasData())) {
console.log("No data for that symbol");
} else {
console.log(prices);
}
MarketDataPromise
class MarketDataPromise<T> extends Promise<T> {
isJson(): boolean;
isCsv(): boolean;
isHtml(): boolean;
readonly no_data: boolean;
hasData(): Promise<boolean>;
save(filename?: string): Promise<string>;
saveToFile(filename?: string): Promise<string>;
blob(): Promise<Blob>;
}
Every resource method returns a MarketDataPromise<T>. It is a regular Promise<T> — you await it to get the data — with a few extra response-model methods available before you unwrap it:
isJson()/isCsv()/isHtml(): reports the requested format.no_data: synchronous flag,truewhen the server returned 404.hasData(): awaits and returns whether the resolved value carries any rows.save(filename?)/saveToFile(filename?): persist the response to disk. Returns aPromise<string>resolving to the path written. Format is inferred from the extension when possible.blob(): materialise the response as aBlob.
Chained saves work without an intermediate await:
const path = await client.stocks
.candles("AAPL", { resolution: "D", countback: 90 })
.saveToFile("aapl-90d.csv");
Accessing Rate Limits
The client tracks rate limits from the API by reading the X-Api-Ratelimit-* headers on every response. The client.rateLimits property is populated by the eager /user/ call during construction (awaited via client.ready).
const client = new MarketDataClient();
await client.ready; // ensures rateLimits is populated
if (client.rateLimits) {
console.log(`Limit: ${client.rateLimits.requestsLimit}`);
console.log(`Remaining: ${client.rateLimits.requestsRemaining}`);
console.log(`Consumed: ${client.rateLimits.requestsConsumed}`);
console.log(`Reset at: ${new Date(client.rateLimits.requestsReset * 1000)}`);
}
UserRateLimits
interface UserRateLimits {
requestsLimit: number; // Total API credits allowed
requestsRemaining: number; // API credits remaining
requestsConsumed: number; // API credits consumed
requestsReset: number; // Unix timestamp when the limit resets
}
Note: Rate limits are tracked via the following response headers:
x-api-ratelimit-limit: Total API credits allowedx-api-ratelimit-remaining: Number of API credits remainingx-api-ratelimit-consumed: Number of API credits consumedx-api-ratelimit-reset: Unix timestamp when the rate limit resets
If rate limits have been fetched and requestsRemaining is 0, the next resource call will fail fast with a RateLimitError rather than hitting the API.
Logging
The SDK includes a built-in logger that outputs diagnostic information. You can pass a custom logger or use the default.
import { MarketDataClient, DefaultLogger, LogLevel } from "@marketdata/sdk";
// Default logger (INFO level)
const client1 = new MarketDataClient();
// Debug logging (more verbose — shows request URLs, response timings, token suffix)
const client2 = new MarketDataClient({ debug: true });
// Custom log level
const logger = new DefaultLogger(LogLevel.WARN);
const client3 = new MarketDataClient({ logger });
Log levels (in order of verbosity): DEBUG, INFO, WARN, ERROR.
You can also set the default level via the MARKETDATA_LOGGING_LEVEL environment variable.
The default logger obfuscates tokens in log output, showing only the last 4 characters.
Configuration
The SDK supports flexible configuration of universal parameters through environment variables, constructor arguments, and per-method overrides. See the Settings documentation for complete details on all available configuration options and how they interact.