# 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

1. Review the [documentation on authentication](https://www.marketdata.app/docs/sdk/js/authentication) to learn how to set your API token.
2. Create a [`MarketDataClient`](#MarketDataClient) instance and use it to make requests to the Market Data API.
3. Make a test request and review the console output. The SDK includes logging capabilities to help you debug requests.
4. Check the [rate limit](#RateLimits) in the client to track credit usage and how many API credits remain.
5. Configure [Settings](https://www.marketdata.app/docs/sdk/js/settings) to customize output format, date format, and other universal parameters.

<a name="MarketDataClient"></a>
## MarketDataClient

```typescript
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](#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](https://www.marketdata.app/docs/sdk/js/authentication) for details.
- `rateLimits` ([UserRateLimits](#RateLimits), optional): Current rate limit information. Populated by the startup `/user/` call (see `ready` below) or by the first successful API request when startup validation is skipped.
- `ready` (Promise&lt;void&gt;): Resolves when the eager `/user/` validation completes. Rejects with `AuthenticationError` on 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&lt;string, string&gt;): HTTP headers including `Authorization` and `User-Agent`.
- `logger` ([Logger](#Logger)): The logger instance used for diagnostic output.
- `settings` ([MarketDataSettings](https://www.marketdata.app/docs/sdk/js/settings)): Resolved configuration including env-var defaults. See [Settings](https://www.marketdata.app/docs/sdk/js/settings) for details.

#### Resources

- `stocks` ([StocksResource](https://www.marketdata.app/docs/sdk/js/stocks)): Access to stocks endpoints (prices, quotes, candles, earnings, news)
- `options` ([OptionsResource](https://www.marketdata.app/docs/sdk/js/options)): Access to options endpoints (chain, expirations, quotes, lookup)
- `funds` ([FundsResource](https://www.marketdata.app/docs/sdk/js/funds)): Access to funds endpoints (candles)
- `markets` ([MarketsResource](https://www.marketdata.app/docs/sdk/js/markets)): Access to markets endpoints (status)
- `utilities` ([UtilitiesResource](https://www.marketdata.app/docs/sdk/js/utilities)): Access to utility endpoints (`status`, `headers`)

<a name="MarketDataClient.constructor"></a>
### constructor

```typescript
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](#MarketDataClient), optional)

  Configuration object. All properties are optional:

  - `token` (string): The authentication token. Falls back to `MARKETDATA_TOKEN` environment variable if not provided.
  - `baseUrl` (string): Override the API base URL. Defaults to `https://api.marketdata.app`.
  - `apiVersion` (string): Override the API version. Defaults to `v1`.
  - `maxRetries` (number): Maximum retry attempts for retriable errors. Defaults to `3`.
  - `retryInitialWait` (number): Initial wait in seconds before the first retry. Defaults to `0.5`.
  - `retryMaxWait` (number): Maximum wait in seconds between retries. Defaults to `10`.
  - `retryFactor` (number): Exponential backoff factor. Defaults to `2`.
  - `skipStartupValidation` (boolean): If `true`, skips the eager `/user/` call the constructor makes to validate the token. Defaults to `false`. Use on serverless platforms where cold-start latency matters.
  - `debug` (boolean): If `true`, sets the default logger level to `DEBUG`. Defaults to `false`.
  - `logger` ([Logger](#Logger)): A custom logger instance. If omitted, the SDK uses its built-in `DefaultLogger`.

#### Returns

- `MarketDataClient`

  A new `MarketDataClient` instance ready to make API requests.

#### Notes

- The client sets a `User-Agent` header of the form `marketdata-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 `fetch` client, 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](https://www.marketdata.app/docs/sdk/js/settings) for the full list and their resolution order.

#### Example

```typescript
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 });
```

<a name="ErrorHandling"></a>
## 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

The idiomatic approach.

```typescript
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);
  }
}
```

### .catch()

Fluent chaining without `try`/`catch`.

```typescript
const prices = await client.stocks
  .prices("AAPL")
  .catch((error) => {
    console.error("Failed:", error.message);
    return [];
  });
```

### Asserting rejections in tests

```typescript
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:

```typescript
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](#NoData). 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)                                                                                                                                                |

<a name="NoData"></a>
### 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:

```typescript
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);
}
```

<a name="MarketDataPromise"></a>
### MarketDataPromise

```typescript
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, `true` when 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 a `Promise<string>` resolving to the path written. Format is inferred from the extension when possible.
- `blob()`: materialise the response as a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob).

Chained saves work without an intermediate `await`:

```typescript
const path = await client.stocks
  .candles("AAPL", { resolution: "D", countback: 90 })
  .saveToFile("aapl-90d.csv");
```

<a name="RateLimits"></a>
## 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`).

```typescript
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

```typescript
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 allowed
- `x-api-ratelimit-remaining`: Number of API credits remaining
- `x-api-ratelimit-consumed`: Number of API credits consumed
- `x-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.

<a name="Logger"></a>
## Logging

The SDK includes a built-in logger that outputs diagnostic information. You can pass a custom logger or use the default.

```typescript
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](https://www.marketdata.app/docs/sdk/js/settings) documentation for complete details on all available configuration options and how they interact.
