sentisense 0.45.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,25 @@
3
3
  [![npm version](https://img.shields.io/npm/v/sentisense.svg)](https://www.npmjs.com/package/sentisense)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- Official JavaScript/TypeScript SDK for the [SentiSense](https://sentisense.ai) market intelligence API.
6
+ Official JavaScript/TypeScript SDK and CLI for the [SentiSense](https://sentisense.ai) market intelligence API: stock prices, news and social sentiment, the SentiSense Score, insider and congressional trading, institutional 13F flows, options positioning, analyst ratings, earnings analysis, and a cross-signal screener.
7
+
8
+ - Full TypeScript support with detailed type definitions
9
+ - Works in Node.js 18+, Deno, Bun, and browsers
10
+ - Zero runtime dependencies (native `fetch`)
11
+ - Namespaced resources (`stocks`, `documents`, `institutional`, ...) and a typed error hierarchy
12
+ - A complete command line interface in the same package, runnable via `npx` with nothing to install
13
+
14
+ Get a free API key at [app.sentisense.ai/get-api-key](https://app.sentisense.ai/get-api-key). Full API docs at [sentisense.ai/docs/api](https://sentisense.ai/docs/api).
15
+
16
+ ## Contents
17
+
18
+ - [Install](#install)
19
+ - [Quick start](#quick-start)
20
+ - [The CLI](#the-cli)
21
+ - [Configuration](#configuration)
22
+ - [Response shapes](#response-shapes)
23
+ - [API reference](#api-reference)
24
+ - [Error handling](#error-handling)
7
25
 
8
26
  ## Install
9
27
 
@@ -11,7 +29,7 @@ Official JavaScript/TypeScript SDK for the [SentiSense](https://sentisense.ai) m
11
29
  npm install sentisense
12
30
  ```
13
31
 
14
- ## Quick Start
32
+ ## Quick start
15
33
 
16
34
  ```typescript
17
35
  import SentiSense from "sentisense";
@@ -26,7 +44,7 @@ console.log(price.currentPrice);
26
44
  const flows = await client.institutional.getFlows();
27
45
  ```
28
46
 
29
- ## CLI
47
+ ## The CLI
30
48
 
31
49
  The same package ships a command line tool. Nothing to install:
32
50
 
@@ -44,8 +62,6 @@ npx -y sentisense@latest auth <your key>
44
62
  npx -y sentisense@latest health
45
63
  ```
46
64
 
47
- Get a key at [app.sentisense.ai/get-api-key](https://app.sentisense.ai/get-api-key).
48
-
49
65
  ### Commands
50
66
 
51
67
  | Command | What you get |
@@ -67,7 +83,7 @@ Get a key at [app.sentisense.ai/get-api-key](https://app.sentisense.ai/get-api-k
67
83
 
68
84
  Run `sentisense help <command>` for its flags and examples.
69
85
 
70
- ### Output
86
+ ### Output modes
71
87
 
72
88
  Readable in a terminal, plain text when piped, and exact API JSON on request:
73
89
 
@@ -77,40 +93,13 @@ npx -y sentisense@latest quote NVDA | cat # plain text, no escape codes
77
93
  npx -y sentisense@latest quote NVDA --json | jq # the API response untouched
78
94
  ```
79
95
 
80
- `--json` prints what the API returned, envelope and all, so `isPreview` and `totalCount` stay
81
- visible. For `quote` that is the exact quote response for one ticker, and an object keyed by
82
- ticker for several. `--full` widens any command. `--no-color` and `NO_COLOR` drop the colour,
83
- `--plain` and `--pretty` force a layout, and `--debug` prints stack traces.
84
-
85
- Commands spend requests on the answer, not on decoration: `quote` looks up the company name
86
- only for the terminal layout, so piped and `--json` output cost one request per ticker. When
87
- something supplementary does not come back, such as the Score history behind a sparkline, the
88
- command still prints its answer and exits 0 with a `note:` line on stderr, so stdout stays
89
- clean for a pipe and the gap is never silent.
90
-
91
- ### Saying who is calling
96
+ `--json` prints what the API returned, envelope and all, so `isPreview` and `totalCount` stay visible. For `quote` that is the exact quote response for one ticker, and an object keyed by ticker for several. `--full` widens any command. `--no-color` and `NO_COLOR` drop the colour, `--plain` and `--pretty` force a layout, and `--debug` prints stack traces.
92
97
 
93
- If you set `SENTISENSE_AGENT_NAME` (what your agent is called) and `SENTISENSE_SKILL` (the
94
- slug of the skill driving it), requests carry that identity, so usage can be understood and
95
- the tools improved. Both are optional, never required, and nothing is inferred when they are
96
- absent.
97
-
98
- ```bash
99
- export SENTISENSE_AGENT_NAME=research-desk
100
- export SENTISENSE_SKILL=stock-analysis
101
- npx -y sentisense@latest quote NVDA
102
- # User-Agent: sentisense-node/0.44.0 sentisense-cli/0.44.0 (stock-analysis; agent/research-desk)
103
- ```
104
-
105
- Either can also be a flag (`--agent`, `--skill`) or a stored setting
106
- (`sentisense auth --agent research-desk --skill stock-analysis`), resolved flag first, then
107
- environment, then config. Values are reduced to letters, digits, dot, underscore and hyphen,
108
- and capped at 32 characters, so nothing you set can reshape the header.
98
+ Commands spend requests on the answer, not on decoration: `quote` looks up the company name only for the terminal layout, so piped and `--json` output cost one request per ticker. When something supplementary does not come back, such as the Score history behind a sparkline, the command still prints its answer and exits 0 with a `note:` line on stderr, so stdout stays clean for a pipe and the gap is never silent.
109
99
 
110
100
  ### Exit codes
111
101
 
112
- Failures print two lines to stderr, what went wrong and what to do about it, and exit with a
113
- code you can branch on. The CLI does not retry, so a 5 is yours to handle.
102
+ Failures print two lines to stderr, what went wrong and what to do about it, and exit with a code you can branch on. The CLI does not retry, so a 5 is yours to handle.
114
103
 
115
104
  | Code | Meaning |
116
105
  |------|---------|
@@ -122,30 +111,52 @@ code you can branch on. The CLI does not retry, so a 5 is yours to handle.
122
111
  | 5 | Rate limited |
123
112
  | 6 | Network failure or timeout |
124
113
 
114
+ ### Saying who is calling
115
+
116
+ If you set `SENTISENSE_AGENT_NAME` (what your agent is called) and `SENTISENSE_SKILL` (the slug of the skill driving it), requests carry that identity, so usage can be understood and the tools improved. Both are optional, never required, and nothing is inferred when they are absent.
117
+
118
+ ```bash
119
+ export SENTISENSE_AGENT_NAME=research-desk
120
+ export SENTISENSE_SKILL=us-stocks-analysis
121
+ npx -y sentisense@latest quote NVDA
122
+ # User-Agent: sentisense-node/{version} sentisense-cli/{version} (us-stocks-analysis; agent/research-desk)
123
+ ```
124
+
125
+ Either can also be a flag (`--agent`, `--skill`) or a stored setting (`sentisense auth --agent research-desk --skill us-stocks-analysis`), resolved flag first, then environment, then config. Values are reduced to letters, digits, dot, underscore and hyphen, and capped at 32 characters, so nothing you set can reshape the header.
126
+
125
127
  Research data, not investment advice.
126
128
 
127
- ## Features
129
+ ## Configuration
128
130
 
129
- - Full TypeScript support with detailed type definitions
130
- - Works in Node.js 18+, Deno, Bun, and browsers
131
- - Zero runtime dependencies (uses native `fetch`)
132
- - Namespaced API resources (stocks, documents, institutional, etc.)
133
- - Typed error hierarchy for clean error handling
131
+ ```typescript
132
+ const client = new SentiSense({
133
+ apiKey: process.env.SENTISENSE_API_KEY, // Get yours at app.sentisense.ai/get-api-key
134
+ baseUrl: "https://...", // Default: https://app.sentisense.ai
135
+ timeout: 30000, // Default: 30s (in milliseconds)
136
+ maxRetries: 3, // Default: 3
137
+ userAgentSuffix: "my-bot/1.4", // Default: none
138
+ });
139
+ ```
140
+
141
+ | Option | Default | What it does |
142
+ |--------|---------|--------------|
143
+ | `apiKey` | none | Sent as `X-SentiSense-API-Key`. Required by every endpoint. |
144
+ | `baseUrl` | `https://app.sentisense.ai` | Override for a non-production host. |
145
+ | `timeout` | `30000` | Per-request timeout in milliseconds. |
146
+ | `maxRetries` | `3` | Retries on 429 and 5xx, honouring `Retry-After`. Set `0` to fail fast. |
147
+ | `userAgentSuffix` | none | Appended to the User-Agent, after `sentisense-node/{version}`. |
148
+
149
+ `userAgentSuffix` is how you say what is calling on top of the SDK, so your traffic is legible in your own logs and in ours. A tool name and version works (`"my-bot/1.4"`), optionally with an agent label (`"my-bot/1.4 agent/research-desk"`). Node only, since browsers set the header themselves. Newlines are collapsed and an empty value is ignored.
150
+
151
+ Keep the key in the environment rather than in source. Committing a literal key leaks it into git history and into every registry security scan that reads your repo.
134
152
 
135
153
  ## Response shapes
136
154
 
137
- Most methods resolve to the payload directly, but two families wrap it. The return types
138
- describe the wrapper, so `.data` / `.documents` type-check natively, no cast.
155
+ Most methods resolve to the payload directly, but two families wrap it. The return types describe the wrapper, so `.data` / `.documents` type-check natively, no cast.
139
156
 
140
- **1. Tier-gated endpoints return a preview envelope.** The payload is in `data`, and
141
- `isPreview` tells you whether it was truncated for your tier. `totalCount` carries the
142
- untruncated size whenever the server knows it: on a truncated response, so you can render
143
- "showing N of M", and on a paged endpoint such as `politicians.getActivity`, where it is
144
- the full match count on every tier including PRO. A missing `totalCount` means "count
145
- `data` yourself", never "zero results".
157
+ **1. Tier-gated endpoints return a preview envelope.** The payload is in `data`, and `isPreview` tells you whether it was truncated for your tier. `totalCount` carries the untruncated size whenever the server knows it: on a truncated response, so you can render "showing N of M", and on a paged endpoint such as `politicians.getActivity`, where it is the full match count on every tier including PRO. A missing `totalCount` means "count `data` yourself", never "zero results".
146
158
 
147
- Affected: `institutional.getFlows` / `getHolders` / `getActivists`, and all five
148
- `insights` methods.
159
+ Affected: `institutional.getFlows` / `getHolders` / `getActivists`, and all five `insights` methods.
149
160
 
150
161
  ```typescript
151
162
  const flows = await client.institutional.getFlows();
@@ -168,11 +179,9 @@ for (const insight of insights.data) {
168
179
  }
169
180
  ```
170
181
 
171
- **2. Document endpoints return a search wrapper.** This is not the preview envelope:
172
- the rows are in `documents` and there is no `isPreview`.
182
+ **2. Document endpoints return a search wrapper.** This is not the preview envelope: the rows are in `documents` and there is no `isPreview`.
173
183
 
174
- Affected: `documents.getByTicker` / `getByTickerRange` / `getByEntity` / `search` /
175
- `getBySource`. Also `stocks.getFundamentalsPeriods`, whose periods are in `periods`.
184
+ Affected: `documents.getByTicker` / `getByTickerRange` / `getByEntity` / `search` / `getBySource`. Also `stocks.getFundamentalsPeriods`, whose periods are in `periods`.
176
185
 
177
186
  ```typescript
178
187
  const results = await client.documents.search("NVDA earnings", { days: 7 });
@@ -182,34 +191,33 @@ for (const doc of results.documents) {
182
191
  }
183
192
  ```
184
193
 
185
- Everything else, including `stocks.getPrice()`, `documents.getStories()`,
186
- `insights.types()` and `institutional.getQuarters()`, resolves to the value itself with no
187
- wrapper.
194
+ Everything else, including `stocks.getPrice()`, `documents.getStories()`, `insights.types()` and `institutional.getQuarters()`, resolves to the value itself with no wrapper.
188
195
 
189
- > **Upgrading from 0.28.x or earlier?** These return types were corrected in 0.29.0. If your
190
- > code read the flat shape (`flows.inflows`, `holders.filter(...)`), it was returning
191
- > `undefined` / throwing at runtime already; switch to `flows.data.inflows` /
192
- > `holders.data.holders`. See [CHANGELOG.md](./CHANGELOG.md) for the full mapping.
196
+ > **Upgrading from 0.28.x or earlier?** These return types were corrected in 0.29.0. If your code read the flat shape (`flows.inflows`, `holders.filter(...)`), it was returning `undefined` / throwing at runtime already; switch to `flows.data.inflows` / `holders.data.holders`. See [CHANGELOG.md](./CHANGELOG.md) for the full mapping.
193
197
 
194
- ## API Reference
198
+ ## API reference
195
199
 
196
200
  ### Stocks
197
201
 
198
202
  ```typescript
199
203
  client.stocks.list() // All ticker symbols
200
204
  client.stocks.listDetailed() // All stocks with details
201
- client.stocks.getPrice("AAPL") // Real-time price
205
+ client.stocks.getPrice("AAPL") // Latest price
202
206
  client.stocks.getPrices(["AAPL", "NVDA"]) // Batch prices
207
+ client.stocks.getQuote("AAPL") // Fuller quote: ranges, market cap, P/E
203
208
  client.stocks.getProfile("AAPL") // Company profile
204
209
  client.stocks.getChart("AAPL", { timeframe: "6M" }) // OHLCV chart data
205
210
  client.stocks.getMarketStatus() // Market open/closed
206
211
  client.stocks.getFundamentals("AAPL") // Financial data
207
212
  client.stocks.getShortInterest("GME") // Short interest
208
- client.stocks.getOptionsSummary("NVDA") // End-of-day options dossier
213
+ client.stocks.getOptionsSummary("NVDA") // End-of-day options dossier
214
+ client.stocks.getOptionsHistory("NVDA", { window: "2y" }) // Daily options aggregates over time
209
215
  client.stocks.getAISummary("AAPL", { depth: "deep" }) // AI report (PRO)
210
216
  ```
211
217
 
212
- ### Documents & News
218
+ Price fields carry `priceAsOf` (Unix milliseconds) for the age of the market data; read that for freshness rather than `timestamp`, which is when the response was served.
219
+
220
+ ### Documents & news
213
221
 
214
222
  ```typescript
215
223
  client.documents.getByTicker("AAPL", { source: "news", days: 3 })
@@ -218,7 +226,7 @@ client.documents.getStories({ limit: 10 })
218
226
  client.documents.getStoryDetail("cluster_abc123")
219
227
  ```
220
228
 
221
- ### Institutional Flows (13F)
229
+ ### Institutional flows (13F)
222
230
 
223
231
  ```typescript
224
232
  client.institutional.getQuarters()
@@ -227,12 +235,7 @@ client.institutional.getHolders("AAPL", "2025-02-14")
227
235
  client.institutional.getActivists("2025-02-14")
228
236
  ```
229
237
 
230
- #### Paging the holder list
231
-
232
- A widely held ticker returns thousands of rows: a megacap quarter is roughly 6,000
233
- holders and 1.5 MB on the wire. Pass `limit` unless you really want the whole list.
234
- Omitting the options object sends the original unbounded request, so existing code
235
- keeps working.
238
+ **Paging the holder list.** A widely held ticker returns thousands of rows: a megacap quarter is roughly 6,000 holders and 1.5 MB on the wire. Pass `limit` unless you really want the whole list; omitting the options object sends the original unbounded request, so existing code keeps working.
236
239
 
237
240
  | Option | Values |
238
241
  |--------|--------|
@@ -241,14 +244,9 @@ keeps working.
241
244
  | `sortBy` | `"shares"` (server default), `"valueUsd"`, or `"sharesChangePct"`. Requires `limit`. |
242
245
  | `sortDir` | `"desc"` (server default) or `"asc"`. Requires `limit`. |
243
246
 
244
- `limit` is the switch for the whole set. Send `offset`, `sortBy`, or `sortDir` without it
245
- and the server ignores them, returning the full unsorted list with a 200 and no warning.
247
+ `limit` is the switch for the whole set: send `offset`, `sortBy`, or `sortDir` without it and the server ignores them, returning the full unsorted list with a 200 and no warning.
246
248
 
247
249
  ```typescript
248
- import SentiSense from "sentisense";
249
-
250
- const client = new SentiSense({ apiKey: process.env.SENTISENSE_API_KEY });
251
-
252
250
  // Top 10 holders by position value, largest first
253
251
  const top = await client.institutional.getHolders("AAPL", "2026-03-31", {
254
252
  limit: 10,
@@ -267,39 +265,19 @@ const page = await client.institutional.getHolders("AAPL", "2026-03-31", {
267
265
  console.log(`${page.data.holders.length} rows of ${page.data.holderCount}`);
268
266
  ```
269
267
 
270
- A response to a request carrying `limit` also has three fields the unbounded response does
271
- not: `returnedCount` (rows on this page, smaller than your `limit` on the last one),
272
- `offset` (echoed back), and `notableChanges`, a ticker-wide summary of the quarter's biggest
273
- position moves so you do not have to scan every page to find them.
268
+ A response to a request carrying `limit` also has three fields the unbounded response does not: `returnedCount` (rows on this page, smaller than your `limit` on the last one), `offset` (echoed back), and `notableChanges`, a ticker-wide summary of the quarter's biggest position moves so you do not have to scan every page to find them. Each holder row also carries `entitySlug`, which you can hand straight to `institutional.getInstitutionDetail()`, and `cikCount` when the row rolls up several SEC filers under one manager; both are null for filers not matched to an institution page, so check before building a link.
274
269
 
275
- ```typescript
276
- const page = await client.institutional.getHolders("AAPL", "2026-03-31", { limit: 100 });
277
- console.log(`${page.data.returnedCount} of ${page.data.holderCount} holders`);
278
- for (const mover of page.data.notableChanges?.top ?? []) {
279
- console.log(mover.filerName, mover.changeType, mover.sharesChangePct);
280
- }
281
- ```
282
-
283
- Each holder row also carries `entitySlug`, which you can hand straight to
284
- `institutional.getInstitutionDetail()`, and `cikCount` when the row rolls up several SEC
285
- filers under one manager. Both are null for filers we have not matched to an institution
286
- page, so check before building a link.
287
-
288
- ### Congressional Trading
270
+ ### Congressional trading
289
271
 
290
272
  ```typescript
291
273
  client.politicians.getActivity({ lookbackDays: 90 }) // Market-wide STOCK Act feed
292
274
  client.politicians.getFilings("NVDA") // Trades in one stock
293
275
  client.politicians.getMembers() // Tracked members + trade stats
294
276
  client.politicians.getMember("nancy-pelosi") // One member's profile and trades
277
+ client.politicians.getDirectory({ q: "tex" }) // Discover slugs, including former members
295
278
  ```
296
279
 
297
- #### Paging the activity feed
298
-
299
- A 90-day window is routinely well over a thousand disclosures, and without `limit` the
300
- server returns the first 200 with nothing in the payload to say it stopped. `totalCount` on
301
- the envelope is the real size on every tier, so size the walk from that rather than from
302
- `data.length`.
280
+ **Paging the activity feed.** A 90-day window is routinely well over a thousand disclosures, and without `limit` the server returns the first 200 with nothing in the payload to say it stopped. `totalCount` on the envelope is the real size on every tier, so size the walk from that rather than from `data.length`.
303
281
 
304
282
  | Option | Values |
305
283
  |--------|--------|
@@ -319,63 +297,37 @@ for (let offset = 100; offset < (first.totalCount ?? 0); offset += 100) {
319
297
  }
320
298
  ```
321
299
 
322
- ### Entity Metrics
300
+ ### Insider trading (Form 4)
323
301
 
324
302
  ```typescript
325
- // Time-series metrics (v2 API)
326
- client.entityMetrics.getMetrics("AAPL", { metricType: "sentiment" })
327
- client.entityMetrics.getMetrics("AAPL", {
328
- metricType: "mentions",
329
- startTime: Date.now() - 7 * 86400000,
330
- endTime: Date.now(),
331
- maxDataPoints: 100,
332
- })
333
-
334
- // Distribution by source
335
- client.entityMetrics.getDistribution("AAPL", "sentiment")
336
- client.entityMetrics.getDistribution("AAPL", "mentions", { dimension: "source" })
303
+ client.insider.getActivity({ lookbackDays: 30 }) // Market-wide buys and sells by ticker
304
+ client.insider.getTrades("NVDA", { lookbackDays: 90 }) // Individual filed transactions
305
+ client.insider.getClusterBuys({ lookbackDays: 90 }) // 3+ distinct insiders buying the same stock
337
306
  ```
338
307
 
339
- Available metric types: `mentions`, `sentiment`, `sentisense`, `social_dominance`, `creators`.
340
-
341
- ### Knowledge Base
308
+ Each trade row carries both the raw SEC `transactionCode` and a simplified `transactionType`. Only codes `P` and `S` are open-market trades; awards, gifts, exercises, and code `F` (shares withheld to cover taxes at vest, served as `SELL`) are corporate mechanics, so read `transactionCode` when you tally discretionary buying or selling. The market-wide activity endpoint's sells already exclude code `F` server-side.
342
309
 
343
- ```typescript
344
- client.kb.getPopularEntities()
345
- ```
310
+ ### Analyst ratings
346
311
 
347
- ### Analyst Ratings
348
-
349
- The **price target cone** (mean, high, low, upside %) and consensus are **free for everyone, full data via API**: we give it away. Upgrade/downgrade feeds and forward EPS estimates are limited on free, unlimited on PRO.
312
+ The price target cone (mean, high, low, upside %) and consensus are free for everyone with full data. Upgrade/downgrade feeds and forward EPS estimates are limited on free, unlimited on PRO.
350
313
 
351
314
  ```typescript
352
- client.analyst.consensus("AAPL") // Price target cone + consensus. Free for everyone, full data.
353
- client.analyst.actions("AAPL", { lookbackDays: 30 }) // Upgrade/downgrade feed. Free: 3 most recent. PRO: unlimited.
354
- client.analyst.estimates("AAPL") // Forward EPS + earnings surprises. Free: 1 quarter. PRO: full history.
315
+ client.analyst.consensus("AAPL") // Price target cone + consensus. Free, full data.
316
+ client.analyst.actions("AAPL", { lookbackDays: 30 }) // Upgrade/downgrade feed. Free: 3 most recent.
317
+ client.analyst.estimates("AAPL") // Forward EPS + surprises. Free: 1 quarter.
355
318
  client.analyst.marketActivity({ lookbackDays: 7 }) // Market-wide analyst actions (PRO).
356
319
  ```
357
320
 
358
- ### Company KPIs (PRO)
359
-
360
- ```typescript
361
- client.stocks.getKpis("AAPL") // Product metrics and segment revenue time-series. Free returns metadata only (empty kpis array); PRO returns full series.
362
- client.stocks.listKpiCoverage() // All tickers with curated KPI data (free, no quota cost)
363
- ```
364
-
365
321
  ### Earnings
366
322
 
367
323
  The earnings analysis report is the assembled version of a quarter: one object per fiscal period carrying the editorial headline, the KPI cards with year-over-year deltas, the guidance language as management phrased it, and a summary of the earnings call. Pair it with the recent-reporters feed to drive a post-earnings sweep. Both return the preview envelope.
368
324
 
369
325
  ```typescript
370
- client.earnings.getSummaries("AAPL", { limit: 4 }) // Per-quarter analysis report, newest first. FREE: latest quarter, shaped. PRO: every hydrated quarter in full.
326
+ client.earnings.getSummaries("AAPL", { limit: 4 }) // Per-quarter analysis, newest first. Free: latest quarter, shaped.
371
327
  client.earnings.getRecent({ days: 7, limit: 25 }) // Who reported in the last N days. Full window on every key.
372
328
  ```
373
329
 
374
330
  ```typescript
375
- import SentiSense from "sentisense";
376
-
377
- const client = new SentiSense({ apiKey: process.env.SENTISENSE_API_KEY });
378
-
379
331
  const res = await client.earnings.getSummaries("AAPL", { limit: 1 });
380
332
  const quarter = res.data[0];
381
333
 
@@ -397,22 +349,63 @@ if (quarter) {
397
349
 
398
350
  The forward-looking half of the family is `client.calendar.getEarnings()`, which covers scheduled dates and consensus EPS rather than results.
399
351
 
352
+ ### Company KPIs (PRO)
353
+
354
+ ```typescript
355
+ client.stocks.getKpis("AAPL") // Product metrics and segment revenue. Free: metadata only. PRO: full series.
356
+ client.stocks.listKpiCoverage() // All tickers with curated KPI data (free, no quota cost)
357
+ ```
358
+
400
359
  ### ETFs (beta)
401
360
 
402
- Composition data is public; the holdings-weighted aggregate views follow the same PRO-with-preview pattern as Analyst/Insider. Aggregates synthesize fund-level views from each constituent's per-stock data, weighted by allocation, with a `coverage` block on every response.
361
+ Composition data is public; the holdings-weighted aggregate views follow the same PRO-with-preview pattern as analyst and insider data. Aggregates synthesize fund-level views from each constituent's per-stock data, weighted by allocation, with a `coverage` block on every response.
362
+
363
+ ```typescript
364
+ client.etfs.list() // Every ETF tracked
365
+ client.etfs.holdings("QQQ") // Full composition + freshness metadata
366
+ client.etfs.analystAggregate("QQQ") // Holdings-weighted analyst consensus
367
+ client.etfs.insiderAggregate("ARKK", { lookbackDays: 90 }) // Holdings-weighted Form 4 net flow
368
+ client.etfs.sentimentAggregate("QQQ") // Constituent-weighted vs direct Score
369
+ ```
370
+
371
+ ### Entity metrics
372
+
373
+ ```typescript
374
+ // Time-series metrics (v2 API)
375
+ client.entityMetrics.getMetrics("AAPL", { metricType: "sentiment" })
376
+ client.entityMetrics.getMetrics("AAPL", {
377
+ metricType: "mentions",
378
+ startTime: Date.now() - 7 * 86400000,
379
+ endTime: Date.now(),
380
+ maxDataPoints: 100,
381
+ })
382
+
383
+ // Distribution by source
384
+ client.entityMetrics.getDistribution("AAPL", "sentiment")
385
+ client.entityMetrics.getDistribution("AAPL", "mentions", { dimension: "source" })
386
+ ```
387
+
388
+ Available metric types: `mentions`, `sentiment`, `sentisense`, `social_dominance`, `creators`.
389
+
390
+ ### Options
391
+
392
+ End-of-day options positioning: where implied volatility, put/call flow and skew are unusual today, and how a name's readings have trended.
403
393
 
404
394
  ```typescript
405
- client.etfs.list() // Every ETF tracked. Returns ticker, name, issuer, tracked index, asset class.
406
- client.etfs.holdings("QQQ") // Full composition: per-holding weights + freshness metadata.
407
- client.etfs.analystAggregate("QQQ") // Holdings-weighted analyst consensus. Free: headline + coverage. PRO: + topContributors.
408
- client.etfs.insiderAggregate("ARKK", { lookbackDays: 90 }) // Holdings-weighted Form 4 net flow. Free: headline. PRO: + topContributors.
409
- client.etfs.sentimentAggregate("QQQ") // SentiSense readings side-by-side: constituent-weighted vs direct.
395
+ client.options.getOverview() // Market-wide radar, ranked
396
+ client.stocks.getOptionsSummary("NVDA") // One name's full dossier
397
+ client.stocks.getOptionsHistory("NVDA", { window: "2y" }) // That name's daily series
410
398
  ```
411
399
 
412
- ### Market Mood
400
+ The radar carries two separately-ranked boards: `data.rows` for stocks and `data.etfRows` for ETFs. Keep them apart. Every reading behind a row's `interestScore` is a percentile of that ticker's own trailing history, so a ranking built across both boards compares numbers measured against different baselines. The aggregates split the same way, with the `etf`-prefixed fields describing the ETF board alone.
401
+
402
+ A row whose baseline is still building carries its raw readings with the percentiles and `interestScore` omitted, which means "not enough history yet" rather than "nothing interesting". `getOptionsSummary` reports an uncovered ticker as a `null` payload; `getOptionsHistory` reports it as an empty `series` instead, so check the array rather than null-checking there.
403
+
404
+ ### Market mood & knowledge base
413
405
 
414
406
  ```typescript
415
- client.marketMood.get()
407
+ client.marketMood.get() // Composite market sentiment with sub-signals
408
+ client.kb.getPopularEntities() // Most-tracked entities
416
409
  ```
417
410
 
418
411
  ### Screener
@@ -456,15 +449,15 @@ Three field semantics are worth stating outright, because guessing them wrong pr
456
449
 
457
450
  - **`ANALYST_RATING_MEAN` is inverted.** It is the vendor's 1-to-5 scale where **1.0 is strong buy**, so bullish is `LTE 2.5`. Prefer `ANALYST_BUY_RATIO_PCT`, which runs the intuitive direction.
458
451
  - **`MA_CROSS_STATE` is ordinal**, not a percentage: `1` golden cross, `-1` death cross, `0` neither. Use `EQ`.
459
- - **`SENTIMENT_DIRECTION` is the sign of the 7-day SentiSense Score** (`1` / `0` / `-1`) with a neutral band of plus-or-minus 5. Despite the name it is not sentiment polarity, and `0` matches only an exact zero.
452
+ - **`SENTIMENT_DIRECTION` is the sign of the 7-day SentiSense Score** (`1` / `0` / `-1`) with a neutral band of plus-or-minus 5. Despite the name it is not sentiment polarity.
460
453
 
461
454
  The Score fields (`SENTI_SCORE_7D`, `SENTI_SCORE_1M`, `SCORE_CHANGE_7D`) are the SentiSense Score, not polarity: unbounded, banded at 5 / 13 / 23 either side of zero. Filter on those band edges, not on values like `0.5`, which behave as "any positive score". Nulls never match in either direction, so `RETURN_1Y >= 0` and `RETURN_1Y < 0` do not partition the universe: a stock listed four months ago is in neither result. If a screen returns fewer rows than you expect, check coverage before you check your thresholds.
462
455
 
463
456
  On the ETF side, `CONSTITUENTS_WEIGHTED_SENTISENSE` is the holdings-weighted Score across what the fund owns and is usually the one you want; `DIRECT_SENTISENSE` is the Score from chatter about the fund ticker itself. `WEIGHT_COVERED_PCT` tells you how much of the fund's weight had constituent data behind the weighted number.
464
457
 
465
- Screens read a snapshot that refreshes every 20 minutes, so this is not a quote feed. Use `client.stocks.getQuote()` for live prices.
458
+ Screens read a snapshot that refreshes every 20 minutes, so this is not a quote feed. Use `client.stocks.getQuote()` for current quotes.
466
459
 
467
- ## Error Handling
460
+ ## Error handling
468
461
 
469
462
  ```typescript
470
463
  import SentiSense, { AuthenticationError, RateLimitError } from "sentisense";
@@ -480,7 +473,7 @@ try {
480
473
  }
481
474
  ```
482
475
 
483
- | Error Class | HTTP Status | When |
476
+ | Error class | HTTP status | When |
484
477
  |------------|-------------|------|
485
478
  | `AuthenticationError` | 401, 403 | Invalid API key or insufficient tier |
486
479
  | `NotFoundError` | 404 | Resource not found |
@@ -489,39 +482,13 @@ try {
489
482
 
490
483
  All errors extend `SentiSenseError` and include `status`, `code`, and `message` properties.
491
484
 
492
- ## Configuration
493
-
494
- ```typescript
495
- const client = new SentiSense({
496
- apiKey: process.env.SENTISENSE_API_KEY, // Get yours at app.sentisense.ai/settings/developer
497
- baseUrl: "https://...", // Default: https://app.sentisense.ai
498
- timeout: 30000, // Default: 30s (in milliseconds)
499
- maxRetries: 3, // Default: 3
500
- userAgentSuffix: "my-bot/1.4", // Default: none
501
- });
502
- ```
503
-
504
- | Option | Default | What it does |
505
- |--------|---------|--------------|
506
- | `apiKey` | none | Sent as `X-SentiSense-API-Key`. Required by every endpoint. |
507
- | `baseUrl` | `https://app.sentisense.ai` | Override for a non-production host. |
508
- | `timeout` | `30000` | Per-request timeout in milliseconds. |
509
- | `maxRetries` | `3` | Retries on 429 and 5xx, honouring `Retry-After`. Set `0` to fail fast. |
510
- | `userAgentSuffix` | none | Appended to the User-Agent, after `sentisense-node/{version}`. |
511
-
512
- `userAgentSuffix` is how you say what is calling on top of the SDK, so your traffic is legible
513
- in your own logs and in ours. A tool name and version works (`"my-bot/1.4"`), optionally with
514
- an agent label (`"my-bot/1.4 agent/research-desk"`). Node only, since browsers set the header
515
- themselves. Newlines are collapsed and an empty value is ignored.
516
-
517
- Keep the key in the environment rather than in source. Committing a literal key leaks it
518
- into git history and into every registry security scan that reads your repo.
519
-
520
- ## Get an API Key
485
+ ## Links
521
486
 
522
- Generate your API key from the [Developer Console](https://app.sentisense.ai/settings/developer).
487
+ - Get a free API key: [app.sentisense.ai/get-api-key](https://app.sentisense.ai/get-api-key)
488
+ - API documentation: [sentisense.ai/docs/api](https://sentisense.ai/docs/api)
489
+ - Changelog: [CHANGELOG.md](./CHANGELOG.md)
523
490
 
524
- For full API documentation, see [sentisense.ai/docs/api](https://sentisense.ai/docs/api).
491
+ SentiSense provides research data for informational and educational purposes, not investment advice.
525
492
 
526
493
  ## License
527
494