> For the complete documentation index, see [llms.txt](https://docs.infoway.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.infoway.io/en-docs/sdk-and-developer-tools/infoway-node.js-sdk.md).

# Infoway Node.js SDK

Official Infoway Node.js / TypeScript SDK for REST market data, fundamentals, and WebSocket streams.

[![npm version](https://img.shields.io/npm/v/infoway-sdk.svg)](https://www.npmjs.com/package/infoway-sdk) [![Node.js](https://img.shields.io/node/v/infoway-sdk.svg)](https://www.npmjs.com/package/infoway-sdk) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

| Item             | Description                                                                                                                                           |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Package          | [`infoway-sdk@0.4.1`](https://www.npmjs.com/package/infoway-sdk)                                                                                      |
| Runtime          | Node.js 18+                                                                                                                                           |
| REST             | `https://data.infoway.io`                                                                                                                             |
| Quotes WebSocket | `wss://data.infoway.io/ws`                                                                                                                            |
| News WebSocket   | `wss://data.infoway.io/news`                                                                                                                          |
| Rate limits      | [REST](/en-docs/getting-started/api-limitation/rest-api-limitation.md) · [WebSocket](/en-docs/getting-started/api-limitation/websocket-limitation.md) |
| Error codes      | [REST](/en-docs/getting-started/error-codes/rest-api-error-codes.md) · [WebSocket](/en-docs/getting-started/error-codes/websocket-error-codes.md)     |
| Endpoints        | [Endpoints](/en-docs/getting-started/endpoints.md)                                                                                                    |

If `apiKey` is omitted, the SDK reads `INFOWAY_API_KEY`. REST methods are `async`.

### Contents

* Install
* Quick start
* Symbols
* Client
* REST
* Typed results
* WebSocket
* Error codes
* Changelog
* REST paths

### Install

```bash
npm install infoway-sdk
```

### Quick start

```typescript
import { InfowayClient, KlineType } from "infoway-sdk";

const client = new InfowayClient();
console.log(await client.stock.getTrade("AAPL.US"));
console.log(await client.crypto.getKline("BTCUSDT", KlineType.DAY, 30));
console.log(await client.packages.getInfo());
```

### Symbols

| Market    | Format          | Valid         | Invalid     |
| --------- | --------------- | ------------- | ----------- |
| US        | `{code}.US`     | `AAPL.US`     | `AAPL`      |
| Hong Kong | 5-digit + `.HK` | `00700.HK`    | `700.HK`    |
| Shanghai  | `{code}.SH`     | `600519.SH`   | `600519.CN` |
| Shenzhen  | `{code}.SZ`     | `000001.SZ`   | `000001.CN` |
| Japan     | `{code}.JP`     | `7203.JP`     |             |
| Korea     | `{code}.KS`     | `005930.KS`   |             |
| India     | `{code}.IN`     | `RELIANCE.IN` |             |
| Taiwan    | `{code}.TW`     | `2330.TW`     |             |
| Crypto    | Pair            | `BTCUSDT`     |             |
| FX        | Pair            | `USDJPY`      |             |

`basic` / `financial` take a product `type` such as `STOCK_US` or `CRYPTO`, not a market code like `US`.

### Client

| Parameter    | Default                   | Description                        |
| ------------ | ------------------------- | ---------------------------------- |
| `apiKey`     | `INFOWAY_API_KEY`         | API key                            |
| `baseUrl`    | `https://data.infoway.io` | REST base URL                      |
| `timeout`    | `15000`                   | Per-request timeout (milliseconds) |
| `maxRetries` | `3`                       | Retry count                        |

| Client                                                                 | Use                      |
| ---------------------------------------------------------------------- | ------------------------ |
| `stock` / `crypto` / `japan` / `india` / `korea` / `taiwan` / `common` | Trade, depth, candles    |
| `basic` / `packages`                                                   | Symbols, calendar, plan  |
| `market` / `plate`                                                     | Overview, sectors        |
| `stockInfo` / `financial`                                              | Fundamentals, statements |

Prefer enums: `KlineType`, `SymbolType`, `Market`, `Lang`, `NewsLang`, `PeriodType`, `Business`, `RankSort`, `SortOrder`, `ScheduleType`.

### REST

Join symbols with commas. Rate limits: [REST API Limitation](/en-docs/getting-started/api-limitation/rest-api-limitation.md).

#### Quotes

| Method                                        | Description  |
| --------------------------------------------- | ------------ |
| `getTrade(codes)`                             | Latest trade |
| `getDepth(codes)`                             | Order book   |
| `getKline(codes, klineType, count, options?)` | Candles      |

Trade fields: `s` symbol, `p` price, `v` volume, `vw` turnover, `t` milliseconds, `td` side. Candles are under `respList`; `t` is seconds. Up to 500 bars per symbol; multi-symbol calls return 2 bars each.

```typescript
await client.stock.getTrade("AAPL.US,TSLA.US");
await client.crypto.getDepth("BTCUSDT");
await client.korea.getTrade("005930.KS");
await client.taiwan.getTrade("2330.TW");
await client.crypto.getKline("BTCUSDT", KlineType.MIN_1, 100);
```

| Enum                                          | Value | Interval        |
| --------------------------------------------- | ----- | --------------- |
| `MIN_1` / `MIN_5` / `MIN_15` / `MIN_30`       | 1–4   | Minutes         |
| `HOUR_1` / `HOUR_2` / `HOUR_4`                | 5–7   | Hours           |
| `DAY` / `WEEK` / `MONTH` / `QUARTER` / `YEAR` | 8–12  | Daily and above |

#### Basic info

Dates use `YYYYMMDD`. `getTradingHours` is deprecated; use `getTradingSchedule`.

```typescript
import { Market, ScheduleType, SymbolType } from "infoway-sdk";

await client.basic.getSymbols(SymbolType.STOCK_US);
await client.basic.getSymbolInfo(SymbolType.STOCK_US, "AAPL.US");
await client.basic.getStockDetail(SymbolType.STOCK_US, "AAPL.US");
await client.basic.getAdjustmentFactors("AAPL.US", Market.US, "20260801", "20260815");
await client.basic.getTradingDays(Market.US, "20260801", "20260815");
await client.basic.getTradingSchedule();
await client.basic.getTradingScheduleByType(ScheduleType.ENERGY);
await client.basic.getMarkets();
await client.packages.getInfo();
```

#### Overview / sectors / stock info / financials

```typescript
import { Lang, Market, PeriodType, RankSort, SortOrder, SymbolType } from "infoway-sdk";

await client.market.getTemperature("HK,US", Lang.ZH_CN);
await client.market.getBreadth(Market.US, Lang.ZH_CN);
await client.market.getTurnover(Market.US);
await client.market.getIndexes(Lang.EN);
await client.market.getLeaders(Market.US, 10);
await client.market.getOverview(Market.US, Lang.ZH_CN);
await client.market.getRankCategories(Market.US);
await client.market.getRank(Market.US, "all", {
  sort: RankSort.CHG,
  order: SortOrder.DESC,
  limit: 30,
});

await client.plate.getIndustry("HK", 200);
await client.plate.getConcept("HK", 100);
await client.plate.getMembers("IN20293.HK");
await client.plate.getIntro("IN20293.HK");
await client.plate.getChart("HK", 50);

await client.stockInfo.getValuation("AAPL.US");
await client.stockInfo.getRatings("AAPL.US");
await client.stockInfo.getCompany("AAPL.US", "zh-CN");
await client.stockInfo.getPanorama("AAPL.US");
await client.stockInfo.getConcepts("AAPL.US");
await client.stockInfo.getEvents("AAPL.US", 20);
await client.stockInfo.getDrivers("AAPL.US");

await client.financial.getEarningStatus("AAPL.US", SymbolType.STOCK_US);
await client.financial.getIncomeStatement("AAPL.US", SymbolType.STOCK_US, PeriodType.FQ);
await client.financial.getRevenue("AAPL.US", SymbolType.STOCK_US);
await client.financial.getCashFlow("AAPL.US", SymbolType.STOCK_US, PeriodType.FY);
await client.financial.getBalanceSheet("AAPL.US", SymbolType.STOCK_US);
await client.financial.getStatistics("AAPL.US", SymbolType.STOCK_US);
await client.financial.getDividend("00700.HK", SymbolType.STOCK_HK);
await client.financial.getDividendPayout("AAPL.US", SymbolType.STOCK_US);
await client.financial.getEarnings("AAPL.US", SymbolType.STOCK_US, PeriodType.FQ);
```

Rank `key` values come from `getRankCategories`. Financial methods require `symbol` and `type`. `periodType`: `fq` quarter, `fy` year, `fh` half-year.

### Typed results

Pass `{ parse: true }`.

```typescript
const trades = await client.crypto.getTrade("BTCUSDT", { parse: true });
trades[0].price;
trades[0].time;
trades[0].turnover;

const candles = await client.crypto.getKline("BTCUSDT", KlineType.MIN_1, 100, { parse: true });
candles[0].close;
candles[0].change_percent;
```

| Raw                                            | Normalized          |
| ---------------------------------------------- | ------------------- |
| Price / volume as strings                      | `number`            |
| Trade / depth `t` in ms; candle `t` in seconds | `Date`              |
| REST `pc` / WS `pfr`                           | `change_percent`    |
| Candle `respList`                              | flattened list      |
| Book `a`/`b` columns                           | `{ price, volume }` |
| `vw`                                           | `turnover`          |

### WebSocket

#### Quotes

`business` must match the symbol market. `connect()` blocks — keep the promise. 60 frames per minute per connection. See [WebSocket Limitation](/en-docs/getting-started/api-limitation/websocket-limitation.md).

```typescript
import { InfowayWebSocket, KlineType } from "infoway-sdk";

const ws = new InfowayWebSocket({ business: "crypto" });
ws.onTrade = (data) => console.log(data.s, data.p);
ws.onError = (err) => console.error(err);

const running = ws.connect();
ws.subscribeTrade("BTCUSDT,ETHUSDT");
ws.subscribeKline("BTCUSDT", KlineType.MIN_1);
ws.unsubscribeKline("BTCUSDT", KlineType.MIN_1);
ws.close();
await running;
```

Equity trade types: `subscribeTrade(codes, true)`.

| Behavior             | Description                                                                                                       |
| -------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Heartbeat            | `10010` every 30 seconds; server does not reply                                                                   |
| Reconnect            | Replays the current subscription set                                                                              |
| `onReconnect`        | After a later successful open                                                                                     |
| `onDisconnect`       | Unexpected drop only. `close()` does not fire it                                                                  |
| HTTP 401             | Stops reconnecting                                                                                                |
| Quota exceeded (516) | Retried on the same socket with backoff instead of closing it; first delay `subscribeRetryMs` (default 2000)      |
| Stale resubscribe    | After a push has been seen, silence beyond `staleAfterMs` (default 90000, `0` disables) resends the subscriptions |
| `Subscribe fail:`    | A plain-text subscribe failure is delivered to `onError` as `SUBSCRIBE_FAIL`                                      |

| Dir | Code                  | Description                     |
| --- | --------------------- | ------------------------------- |
| out | 10000 / 10003 / 10006 | Subscribe trade / depth / kline |
| out | 11000 / 11001 / 11002 | Unsubscribe                     |
| out | 10010                 | Heartbeat                       |
| in  | 10002 / 10005 / 10008 | Push                            |
| in  | 11010                 | Unsubscribe ack                 |
| in  | 200                   | Connected                       |

An ack means the request was accepted. Merge symbols into one comma-separated string.

#### News

`wss://data.infoway.io/news` requires a separate entitlement. One news connection per key.

```typescript
import { InfowayNewsWebSocket, NewsLang } from "infoway-sdk";

const news = new InfowayNewsWebSocket({ lang: NewsLang.ZH_HANS });
news.onNews = (item) => console.log(item.title);
```

Subscribe `10020`, unsubscribe `11020`, push `10022`. A later subscribe replaces the language.

### Error codes

REST uses `ret`. WebSocket uses `code`. `508`–`514` mean different things on each side. Full tables: [REST API Error Codes](/en-docs/getting-started/error-codes/rest-api-error-codes.md) and [WebSocket Error Codes](/en-docs/getting-started/error-codes/websocket-error-codes.md).

```typescript
import { InfowayAPIError, InfowayAuthError, InfowayRateLimitError } from "infoway-sdk";

try {
  await client.stock.getTrade("INVALID");
} catch (e) {
  if (e instanceof InfowayAuthError) console.error(e.msg);
  else if (e instanceof InfowayRateLimitError) console.error(e.ret, e.msg);
  else if (e instanceof InfowayAPIError) console.error(e.message, e.traceId);
}
```

REST budget is about 1200 calls/minute/key. An invalid key raises `InfowayAuthError` on REST; WebSocket handshake HTTP 401 does not reconnect.

| REST `ret` | Description                    |
| ---------- | ------------------------------ |
| 200        | Success                        |
| 400        | Bad request                    |
| 500        | Server error                   |
| 501 / 502  | Rate limit                     |
| 503        | Candle count exceeded          |
| 505        | Symbol count exceeded          |
| 506 / 507  | Invalid / missing parameter    |
| 508        | Symbol not found               |
| 509        | Permission expired             |
| 513        | Timestamp outside plan history |
| 514        | No permission                  |

| WebSocket `code` | Description                                                             |
| ---------------- | ----------------------------------------------------------------------- |
| 501 / 502        | Rate limit                                                              |
| 505              | Too many products on this connection (the SDK stops resending)          |
| 516              | Account-wide subscription quota exceeded (the SDK retries with backoff) |
| 506 / 507        | Invalid / missing parameter                                             |
| 508–511          | API key expired / invalid / empty / blacklisted                         |
| 512              | Connection count exceeded                                               |
| 513              | Heartbeat timeout                                                       |
| 515              | Not JSON                                                                |
| 517–521          | Handshake failed                                                        |

### Changelog

**0.4.1** — A subscribe quota rejection (516) no longer terminates the socket: subscriptions are retried on the same connection with backoff, and both the retries and the watchdog resends stay inside the 60-frames-per-minute budget. A plain-text `Subscribe fail:` reply is surfaced on `onError` as `SUBSCRIBE_FAIL`. After a 505 the watchdog stops resending, and the resend gap widens automatically when many subscription groups are open. New `staleAfterMs` (default 90000, `0` disables) and `subscribeRetryMs` (default 2000). Outbound timestamps are capped at 256 entries. Japan and India examples now use `.JP` / `.IN` (Yahoo's `.T` / `.NS` return 508).

**0.4.0** — Client-side request limits (`MAX_SYMBOLS` 100, `MAX_KLINE_BARS` 500, 2 bars per symbol when batched), financial `type` validated locally, `classify` recovers business codes wrapped in `ret=500`, `isTerminal` stops reconnecting on non-retryable errors, unified API key resolution (an explicit empty string no longer falls back to the environment), sync callbacks accepted, calls after close raise an error.

**0.3.0** — `packages`, `financial`, Korea / Taiwan clients, `RestErrorCode` / `WsErrorCode`, enum parameters (`Lang`, `Market`, `PeriodType`, `RankSort`, `ScheduleType`, `SortOrder`), `InfowayIoError`.

**0.2.0** — `basic.*` parameters fixed (breaking):

| Old                               | New                                                             |
| --------------------------------- | --------------------------------------------------------------- |
| `getSymbols("US")`                | `getSymbols(SymbolType.STOCK_US)`                               |
| `getSymbolInfo("AAPL.US")`        | `getSymbolInfo("STOCK_US", "AAPL.US")`                          |
| `getAdjustmentFactors("AAPL.US")` | `getAdjustmentFactors("AAPL.US", "US", "20260801", "20260814")` |
| `getTradingDays("US")`            | `getTradingDays("US", "20260801", "20260814")`                  |
| `getTradingHours()`               | `getTradingSchedule()`                                          |

WebSocket callbacks now receive `data`. `unsubscribeKline` is per interval. An invalid key stops reconnecting.

### REST paths

`{market}` = `stock` / `crypto` / `japan` / `india` / `korea` / `taiwan` / `common`. Full list: [Endpoints](/en-docs/getting-started/endpoints.md).

| API                             | Path                                |
| ------------------------------- | ----------------------------------- |
| Latest trade                    | `GET /{market}/batch_trade/{codes}` |
| Order book                      | `GET /{market}/batch_depth/{codes}` |
| Candles                         | `POST /{market}/v2/batch_kline`     |
| Symbols / calendar / financials | `GET /common/basic/*`               |
| Overview / sectors / stock info | `GET /common/v2/basic/*`            |
| Package                         | `GET /package/info`                 |

### License

MIT. API key: [infoway.io](https://infoway.io).
