sentisense 0.31.0 → 0.34.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 +105 -6
- package/dist/index.cjs +107 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +310 -26
- package/dist/index.d.ts +310 -26
- package/dist/index.mjs +106 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -6
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ npm install sentisense
|
|
|
16
16
|
```typescript
|
|
17
17
|
import SentiSense from "sentisense";
|
|
18
18
|
|
|
19
|
-
const client = new SentiSense({ apiKey:
|
|
19
|
+
const client = new SentiSense({ apiKey: process.env.SENTISENSE_API_KEY });
|
|
20
20
|
|
|
21
21
|
const price = await client.stocks.getPrice("AAPL");
|
|
22
22
|
console.log(price.currentPrice);
|
|
@@ -40,8 +40,11 @@ Most methods resolve to the payload directly, but two families wrap it. The retu
|
|
|
40
40
|
describe the wrapper, so `.data` / `.documents` type-check natively, no cast.
|
|
41
41
|
|
|
42
42
|
**1. Tier-gated endpoints return a preview envelope.** The payload is in `data`, and
|
|
43
|
-
`isPreview` tells you whether it was truncated for your tier.
|
|
44
|
-
|
|
43
|
+
`isPreview` tells you whether it was truncated for your tier. `totalCount` carries the
|
|
44
|
+
untruncated size whenever the server knows it: on a truncated response, so you can render
|
|
45
|
+
"showing N of M", and on a paged endpoint such as `politicians.getActivity`, where it is
|
|
46
|
+
the full match count on every tier including PRO. A missing `totalCount` means "count
|
|
47
|
+
`data` yourself", never "zero results".
|
|
45
48
|
|
|
46
49
|
Affected: `institutional.getFlows` / `getHolders` / `getActivists`, and all five
|
|
47
50
|
`insights` methods.
|
|
@@ -125,6 +128,98 @@ client.institutional.getHolders("AAPL", "2025-02-14")
|
|
|
125
128
|
client.institutional.getActivists("2025-02-14")
|
|
126
129
|
```
|
|
127
130
|
|
|
131
|
+
#### Paging the holder list
|
|
132
|
+
|
|
133
|
+
A widely held ticker returns thousands of rows: a megacap quarter is roughly 6,000
|
|
134
|
+
holders and 1.5 MB on the wire. Pass `limit` unless you really want the whole list.
|
|
135
|
+
Omitting the options object sends the original unbounded request, so existing code
|
|
136
|
+
keeps working.
|
|
137
|
+
|
|
138
|
+
| Option | Values |
|
|
139
|
+
|--------|--------|
|
|
140
|
+
| `limit` | Maximum rows to return. Must be >= 1; values above 1000 are capped server-side. Omit for the full list. |
|
|
141
|
+
| `offset` | Row offset to start from. Server default is 0. Requires `limit`. |
|
|
142
|
+
| `sortBy` | `"shares"` (server default), `"valueUsd"`, or `"sharesChangePct"`. Requires `limit`. |
|
|
143
|
+
| `sortDir` | `"desc"` (server default) or `"asc"`. Requires `limit`. |
|
|
144
|
+
|
|
145
|
+
`limit` is the switch for the whole set. Send `offset`, `sortBy`, or `sortDir` without it
|
|
146
|
+
and the server ignores them, returning the full unsorted list with a 200 and no warning.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import SentiSense from "sentisense";
|
|
150
|
+
|
|
151
|
+
const client = new SentiSense({ apiKey: process.env.SENTISENSE_API_KEY });
|
|
152
|
+
|
|
153
|
+
// Top 10 holders by position value, largest first
|
|
154
|
+
const top = await client.institutional.getHolders("AAPL", "2026-03-31", {
|
|
155
|
+
limit: 10,
|
|
156
|
+
sortBy: "valueUsd",
|
|
157
|
+
sortDir: "desc",
|
|
158
|
+
});
|
|
159
|
+
for (const holder of top.data.holders) {
|
|
160
|
+
console.log(holder.filerName, holder.valueUsd);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Walk the list a page at a time
|
|
164
|
+
const page = await client.institutional.getHolders("AAPL", "2026-03-31", {
|
|
165
|
+
limit: 100,
|
|
166
|
+
offset: 100,
|
|
167
|
+
});
|
|
168
|
+
console.log(`${page.data.holders.length} rows of ${page.data.holderCount}`);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
A response to a request carrying `limit` also has three fields the unbounded response does
|
|
172
|
+
not: `returnedCount` (rows on this page, smaller than your `limit` on the last one),
|
|
173
|
+
`offset` (echoed back), and `notableChanges`, a ticker-wide summary of the quarter's biggest
|
|
174
|
+
position moves so you do not have to scan every page to find them.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
const page = await client.institutional.getHolders("AAPL", "2026-03-31", { limit: 100 });
|
|
178
|
+
console.log(`${page.data.returnedCount} of ${page.data.holderCount} holders`);
|
|
179
|
+
for (const mover of page.data.notableChanges?.top ?? []) {
|
|
180
|
+
console.log(mover.filerName, mover.changeType, mover.sharesChangePct);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Each holder row also carries `entitySlug`, which you can hand straight to
|
|
185
|
+
`institutional.getInstitutionDetail()`, and `cikCount` when the row rolls up several SEC
|
|
186
|
+
filers under one manager. Both are null for filers we have not matched to an institution
|
|
187
|
+
page, so check before building a link.
|
|
188
|
+
|
|
189
|
+
### Congressional Trading
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
client.politicians.getActivity({ lookbackDays: 90 }) // Market-wide STOCK Act feed
|
|
193
|
+
client.politicians.getFilings("NVDA") // Trades in one stock
|
|
194
|
+
client.politicians.getMembers() // Tracked members + trade stats
|
|
195
|
+
client.politicians.getMember("nancy-pelosi") // One member's profile and trades
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### Paging the activity feed
|
|
199
|
+
|
|
200
|
+
A 90-day window is routinely well over a thousand disclosures, and without `limit` the
|
|
201
|
+
server returns the first 200 with nothing in the payload to say it stopped. `totalCount` on
|
|
202
|
+
the envelope is the real size on every tier, so size the walk from that rather than from
|
|
203
|
+
`data.length`.
|
|
204
|
+
|
|
205
|
+
| Option | Values |
|
|
206
|
+
|--------|--------|
|
|
207
|
+
| `lookbackDays` | Days to look back (1-365). Defaults to 90. |
|
|
208
|
+
| `limit` | Rows to return. Must be >= 1; anything above 500 is capped at 500. Omit for the default 200. |
|
|
209
|
+
| `offset` | Row offset to start from. Defaults to 0. Works with or without `limit`. |
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
const first = await client.politicians.getActivity({ limit: 100 });
|
|
213
|
+
console.log(`${first.data.length} of ${first.totalCount} disclosures`);
|
|
214
|
+
|
|
215
|
+
for (let offset = 100; offset < (first.totalCount ?? 0); offset += 100) {
|
|
216
|
+
const page = await client.politicians.getActivity({ limit: 100, offset });
|
|
217
|
+
for (const trade of page.data) {
|
|
218
|
+
console.log(trade.politicianName, trade.ticker, trade.transactionType);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
128
223
|
### Entity Metrics
|
|
129
224
|
|
|
130
225
|
```typescript
|
|
@@ -215,12 +310,16 @@ All errors extend `SentiSenseError` and include `status`, `code`, and `message`
|
|
|
215
310
|
|
|
216
311
|
```typescript
|
|
217
312
|
const client = new SentiSense({
|
|
218
|
-
apiKey:
|
|
219
|
-
baseUrl: "https://...",
|
|
220
|
-
timeout: 30000,
|
|
313
|
+
apiKey: process.env.SENTISENSE_API_KEY, // Get yours at app.sentisense.ai/settings/developer
|
|
314
|
+
baseUrl: "https://...", // Default: https://app.sentisense.ai
|
|
315
|
+
timeout: 30000, // Default: 30s (in milliseconds)
|
|
316
|
+
maxRetries: 3, // Default: 3
|
|
221
317
|
});
|
|
222
318
|
```
|
|
223
319
|
|
|
320
|
+
Keep the key in the environment rather than in source. Committing a literal key leaks it
|
|
321
|
+
into git history and into every registry security scan that reads your repo.
|
|
322
|
+
|
|
224
323
|
## Get an API Key
|
|
225
324
|
|
|
226
325
|
Generate your API key from the [Developer Console](https://app.sentisense.ai/settings/developer).
|
package/dist/index.cjs
CHANGED
|
@@ -22,6 +22,7 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
APIError: () => APIError,
|
|
24
24
|
AuthenticationError: () => AuthenticationError,
|
|
25
|
+
DeepHistoryUnavailableError: () => DeepHistoryUnavailableError,
|
|
25
26
|
NotFoundError: () => NotFoundError,
|
|
26
27
|
RateLimitError: () => RateLimitError,
|
|
27
28
|
SentiSense: () => SentiSense,
|
|
@@ -52,6 +53,13 @@ var NotFoundError = class extends SentiSenseError {
|
|
|
52
53
|
this.name = "NotFoundError";
|
|
53
54
|
}
|
|
54
55
|
};
|
|
56
|
+
var DeepHistoryUnavailableError = class extends SentiSenseError {
|
|
57
|
+
constructor(message, retryAfter) {
|
|
58
|
+
super(message, 202);
|
|
59
|
+
this.name = "DeepHistoryUnavailableError";
|
|
60
|
+
this.retryAfter = retryAfter;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
55
63
|
var RateLimitError = class extends SentiSenseError {
|
|
56
64
|
constructor(message, code, retryAfter) {
|
|
57
65
|
super(message, 429, code);
|
|
@@ -185,7 +193,8 @@ var EntityMetrics = class {
|
|
|
185
193
|
/**
|
|
186
194
|
* Get time-series metric data for an entity using the v2 Serving Metrics API.
|
|
187
195
|
*
|
|
188
|
-
* @param symbol Ticker symbol (e.g. "AAPL")
|
|
196
|
+
* @param symbol Ticker symbol (e.g. "AAPL") or entity urlSlug (e.g. "Nancy-Pelosi",
|
|
197
|
+
* case-insensitive; discover slugs via stocks.getEntities()).
|
|
189
198
|
* @param options Metric type and optional time range / resolution.
|
|
190
199
|
*/
|
|
191
200
|
async getMetrics(symbol, options = {}) {
|
|
@@ -202,7 +211,7 @@ var EntityMetrics = class {
|
|
|
202
211
|
/**
|
|
203
212
|
* Get distribution data for a metric, broken down by a dimension (default: source).
|
|
204
213
|
*
|
|
205
|
-
* @param symbol Ticker symbol (e.g. "AAPL").
|
|
214
|
+
* @param symbol Ticker symbol (e.g. "AAPL") or entity urlSlug.
|
|
206
215
|
* @param metricType The metric to break down (e.g. "mentions", "sentiment").
|
|
207
216
|
* @param options Optional dimension parameter.
|
|
208
217
|
*/
|
|
@@ -313,6 +322,19 @@ var Politicians = class {
|
|
|
313
322
|
*
|
|
314
323
|
* PRO-gated. Free/unauthenticated users receive a preview (top 5 trades)
|
|
315
324
|
* with `isPreview: true` in the response.
|
|
325
|
+
*
|
|
326
|
+
* The feed is longer than one response: a default 90-day window is routinely well over a
|
|
327
|
+
* thousand disclosures, and without `limit` the server sends the first 200 with no marker
|
|
328
|
+
* that it stopped. `totalCount` on the envelope is the real size on every tier, so page
|
|
329
|
+
* with `limit` and `offset` rather than reading `data.length` as the total.
|
|
330
|
+
*
|
|
331
|
+
* ```typescript
|
|
332
|
+
* const first = await client.politicians.getActivity({ limit: 100 });
|
|
333
|
+
* for (let offset = 100; offset < first.totalCount!; offset += 100) {
|
|
334
|
+
* const page = await client.politicians.getActivity({ limit: 100, offset });
|
|
335
|
+
* // ... page.data
|
|
336
|
+
* }
|
|
337
|
+
* ```
|
|
316
338
|
*/
|
|
317
339
|
async getActivity(options) {
|
|
318
340
|
return this.client.get("/api/v1/politicians/activity", options);
|
|
@@ -453,11 +475,20 @@ var Institutional = class {
|
|
|
453
475
|
* are two levels down: `(await getHolders(t, d)).data.holders`, alongside ticker-level
|
|
454
476
|
* totals like `holderCount`. Free callers get a truncated `holders` array with
|
|
455
477
|
* `isPreview: true`.
|
|
478
|
+
*
|
|
479
|
+
* A widely held ticker returns thousands of rows: a megacap quarter is about
|
|
480
|
+
* 6,000 holders and 1.5 MB. Pass `limit` unless you really want all of them.
|
|
481
|
+
* Omitting `options` sends the original unbounded request.
|
|
482
|
+
*
|
|
483
|
+
* `limit` is the switch for the whole option set. With it, the response also carries
|
|
484
|
+
* `returnedCount`, `offset`, and a `notableChanges` summary, so you can walk the list
|
|
485
|
+
* without re-counting it. Without it, `offset` / `sortBy` / `sortDir` are ignored by the
|
|
486
|
+
* server and you get the full unsorted list back with a 200.
|
|
456
487
|
*/
|
|
457
|
-
async getHolders(ticker, reportDate) {
|
|
488
|
+
async getHolders(ticker, reportDate, options) {
|
|
458
489
|
return this.client.get(
|
|
459
490
|
`/api/v1/institutional/holders/${encodeURIComponent(ticker)}`,
|
|
460
|
-
{ reportDate }
|
|
491
|
+
{ reportDate, ...options }
|
|
461
492
|
);
|
|
462
493
|
}
|
|
463
494
|
/**
|
|
@@ -597,6 +628,22 @@ var Stocks = class {
|
|
|
597
628
|
async getProfile(ticker, options) {
|
|
598
629
|
return this.client.get(`/api/v1/stocks/${encodeURIComponent(ticker)}/profile`, options);
|
|
599
630
|
}
|
|
631
|
+
/**
|
|
632
|
+
* Get the headline sentiment picture for a stock in one call.
|
|
633
|
+
*
|
|
634
|
+
* Returns the SentiSense Score with its 30-day regime, mention volume and social
|
|
635
|
+
* dominance, per-source tone in `bySource`, plus related tickers, story drivers, a
|
|
636
|
+
* narrative and an FAQ. Available in full on every API-key tier.
|
|
637
|
+
*
|
|
638
|
+
* Use `entityMetrics.getMetrics(ticker, "sentiment", ...)` instead when you need a time
|
|
639
|
+
* series over a specific window rather than the headline read. Returns 404 for tickers
|
|
640
|
+
* with no sentiment coverage.
|
|
641
|
+
*/
|
|
642
|
+
async getSentiment(ticker) {
|
|
643
|
+
return this.client.get(
|
|
644
|
+
`/api/v1/stocks/${encodeURIComponent(ticker)}/sentiment`
|
|
645
|
+
);
|
|
646
|
+
}
|
|
600
647
|
/** Get related KB entities (people, products, partners). */
|
|
601
648
|
async getEntities(ticker) {
|
|
602
649
|
return this.client.get(`/api/v1/stocks/${encodeURIComponent(ticker)}/entities`);
|
|
@@ -630,7 +677,13 @@ var Stocks = class {
|
|
|
630
677
|
async getMarketStatus() {
|
|
631
678
|
return this.client.get("/api/v1/stocks/market-status");
|
|
632
679
|
}
|
|
633
|
-
/**
|
|
680
|
+
/**
|
|
681
|
+
* Get financial statement data for one reporting period: income statement, balance sheet,
|
|
682
|
+
* and cash flow, including `capitalExpenditure` and `freeCashFlow`.
|
|
683
|
+
*
|
|
684
|
+
* Capital expenditure is signed as filed, so normally negative. See {@link Fundamentals}
|
|
685
|
+
* for the free-cash-flow relationship and when it is `null`.
|
|
686
|
+
*/
|
|
634
687
|
async getFundamentals(ticker, options) {
|
|
635
688
|
return this.client.get("/api/v1/stocks/fundamentals", { ticker, ...options });
|
|
636
689
|
}
|
|
@@ -689,8 +742,8 @@ var Stocks = class {
|
|
|
689
742
|
return this.client.get("/api/v1/stocks/with-kpis");
|
|
690
743
|
}
|
|
691
744
|
/**
|
|
692
|
-
* List the KPI metadata tuples available for a ticker
|
|
693
|
-
* chartType`
|
|
745
|
+
* List the KPI metadata tuples available for a ticker (`id, name, category,
|
|
746
|
+
* chartType`) without paying the cost of the full series payload. Mirrors
|
|
694
747
|
* the `/api/v1/insights/stock/{ticker}/types` precedent.
|
|
695
748
|
*
|
|
696
749
|
* Auth: API key required, no quota cost. 404 if the ticker has no curated KPIs.
|
|
@@ -708,7 +761,7 @@ var Trackers = class {
|
|
|
708
761
|
this.client = client;
|
|
709
762
|
}
|
|
710
763
|
/**
|
|
711
|
-
* List every publicly-visible tracker
|
|
764
|
+
* List every publicly-visible tracker: id, display name, category,
|
|
712
765
|
* one-line description, and the methodology anchor to link out to.
|
|
713
766
|
*/
|
|
714
767
|
async list() {
|
|
@@ -722,8 +775,8 @@ var Trackers = class {
|
|
|
722
775
|
* `"choropleth"` they live at `data.geo[]`; etc. Dispatch on `viewType`
|
|
723
776
|
* in your renderer.
|
|
724
777
|
*
|
|
725
|
-
* @param trackerId
|
|
726
|
-
* @param params
|
|
778
|
+
* @param trackerId slug from {@link list}, e.g. `"institution-concentration"`.
|
|
779
|
+
* @param params provider-specific query params (e.g. `{ scope: "us" }` for
|
|
727
780
|
* geographically-scoped trackers like hantavirus). Unknown keys are ignored.
|
|
728
781
|
*/
|
|
729
782
|
async get(trackerId, params) {
|
|
@@ -732,7 +785,7 @@ var Trackers = class {
|
|
|
732
785
|
};
|
|
733
786
|
|
|
734
787
|
// src/version.ts
|
|
735
|
-
var VERSION = "0.
|
|
788
|
+
var VERSION = "0.34.0";
|
|
736
789
|
|
|
737
790
|
// src/client.ts
|
|
738
791
|
var DEFAULT_BASE_URL = "https://app.sentisense.ai";
|
|
@@ -740,6 +793,19 @@ var DEFAULT_TIMEOUT = 3e4;
|
|
|
740
793
|
var DEFAULT_MAX_RETRIES = 3;
|
|
741
794
|
var BASE_DELAY_MS = 1e3;
|
|
742
795
|
var MAX_DELAY_MS = 6e4;
|
|
796
|
+
var DEEP_HISTORY_FALLBACK_WAIT_S = 3;
|
|
797
|
+
var MAX_DEEP_HISTORY_WAIT_S = 30;
|
|
798
|
+
var MAX_RATE_LIMIT_WAIT_S = 120;
|
|
799
|
+
var RATE_LIMIT_FALLBACK_WAIT_S = 60;
|
|
800
|
+
function clampRetryAfter(raw, maxWaitS) {
|
|
801
|
+
if (!raw) return void 0;
|
|
802
|
+
const parsed = Number(raw);
|
|
803
|
+
if (!Number.isFinite(parsed)) return void 0;
|
|
804
|
+
return Math.min(Math.max(0.5, parsed), maxWaitS);
|
|
805
|
+
}
|
|
806
|
+
function retryAfterSeconds(raw, defaultS, maxWaitS) {
|
|
807
|
+
return clampRetryAfter(raw, maxWaitS) ?? defaultS;
|
|
808
|
+
}
|
|
743
809
|
function sleep(ms) {
|
|
744
810
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
745
811
|
}
|
|
@@ -790,12 +856,34 @@ var SentiSense = class {
|
|
|
790
856
|
headers,
|
|
791
857
|
signal: controller.signal
|
|
792
858
|
});
|
|
859
|
+
if (response.status === 202) {
|
|
860
|
+
const waitSeconds = retryAfterSeconds(
|
|
861
|
+
response.headers.get("Retry-After"),
|
|
862
|
+
DEEP_HISTORY_FALLBACK_WAIT_S,
|
|
863
|
+
MAX_DEEP_HISTORY_WAIT_S
|
|
864
|
+
);
|
|
865
|
+
try {
|
|
866
|
+
await response.body?.cancel();
|
|
867
|
+
} catch {
|
|
868
|
+
}
|
|
869
|
+
if (attempt < this.maxRetries) {
|
|
870
|
+
delayMs = waitSeconds * 1e3;
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
throw new DeepHistoryUnavailableError(
|
|
874
|
+
"Deep history is still being assembled. Retry in a few seconds.",
|
|
875
|
+
waitSeconds
|
|
876
|
+
);
|
|
877
|
+
}
|
|
793
878
|
if (!response.ok) {
|
|
794
879
|
const isRetryable = response.status === 429 || response.status >= 500;
|
|
795
880
|
if (isRetryable && attempt < this.maxRetries) {
|
|
796
881
|
if (response.status === 429) {
|
|
797
|
-
|
|
798
|
-
|
|
882
|
+
delayMs = retryAfterSeconds(
|
|
883
|
+
response.headers.get("Retry-After"),
|
|
884
|
+
RATE_LIMIT_FALLBACK_WAIT_S,
|
|
885
|
+
MAX_RATE_LIMIT_WAIT_S
|
|
886
|
+
) * 1e3;
|
|
799
887
|
} else {
|
|
800
888
|
delayMs = Math.min(BASE_DELAY_MS * Math.pow(2, attempt), MAX_DELAY_MS) + Math.random() * 1e3;
|
|
801
889
|
}
|
|
@@ -886,8 +974,11 @@ var SentiSense = class {
|
|
|
886
974
|
case 404:
|
|
887
975
|
throw new NotFoundError(message, code);
|
|
888
976
|
case 429: {
|
|
889
|
-
const
|
|
890
|
-
|
|
977
|
+
const retryAfter = clampRetryAfter(
|
|
978
|
+
response.headers.get("Retry-After"),
|
|
979
|
+
MAX_RATE_LIMIT_WAIT_S
|
|
980
|
+
);
|
|
981
|
+
throw new RateLimitError(message, code, retryAfter);
|
|
891
982
|
}
|
|
892
983
|
default:
|
|
893
984
|
throw new APIError(message, response.status, code);
|
|
@@ -898,6 +989,7 @@ var SentiSense = class {
|
|
|
898
989
|
0 && (module.exports = {
|
|
899
990
|
APIError,
|
|
900
991
|
AuthenticationError,
|
|
992
|
+
DeepHistoryUnavailableError,
|
|
901
993
|
NotFoundError,
|
|
902
994
|
RateLimitError,
|
|
903
995
|
SentiSense,
|