sentisense 0.33.0 → 0.35.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
@@ -16,7 +16,7 @@ npm install sentisense
16
16
  ```typescript
17
17
  import SentiSense from "sentisense";
18
18
 
19
- const client = new SentiSense({ apiKey: "ss_live_YOUR_KEY" });
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. On a truncated response
44
- `totalCount` carries the untruncated size, so you can render "showing N of M".
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: "ss_live_YOUR_KEY", // Get yours at app.sentisense.ai/settings/developer
219
- baseUrl: "https://...", // Default: https://app.sentisense.ai
220
- timeout: 30000, // Default: 30s (in milliseconds)
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
@@ -322,6 +322,19 @@ var Politicians = class {
322
322
  *
323
323
  * PRO-gated. Free/unauthenticated users receive a preview (top 5 trades)
324
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
+ * ```
325
338
  */
326
339
  async getActivity(options) {
327
340
  return this.client.get("/api/v1/politicians/activity", options);
@@ -462,11 +475,20 @@ var Institutional = class {
462
475
  * are two levels down: `(await getHolders(t, d)).data.holders`, alongside ticker-level
463
476
  * totals like `holderCount`. Free callers get a truncated `holders` array with
464
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.
465
487
  */
466
- async getHolders(ticker, reportDate) {
488
+ async getHolders(ticker, reportDate, options) {
467
489
  return this.client.get(
468
490
  `/api/v1/institutional/holders/${encodeURIComponent(ticker)}`,
469
- { reportDate }
491
+ { reportDate, ...options }
470
492
  );
471
493
  }
472
494
  /**
@@ -531,7 +553,11 @@ var MarketMoodResource = class {
531
553
  return this.client.get("/api/v2/market-mood");
532
554
  }
533
555
  // TODO: accept a `days` param to control history length (the endpoint supports ?days=N).
534
- // TODO: add a sibling `indexes` resource for /api/v1/indexes/{id} (+ /history), e.g. fed-sentiment.
556
+ //
557
+ // Market Mood is also reachable through `client.indexes`, which serves it in the shared
558
+ // index envelope alongside fed-sentiment and ai-sentiment. Use this resource when you want
559
+ // the phase band, weekly change, per-signal breakdown and per-sector map; use `indexes`
560
+ // when you want every index to answer the same shape. Both report the same headline number.
535
561
  };
536
562
 
537
563
  // src/resources/marketSummary.ts
@@ -626,9 +652,17 @@ var Stocks = class {
626
652
  async getEntities(ticker) {
627
653
  return this.client.get(`/api/v1/stocks/${encodeURIComponent(ticker)}/entities`);
628
654
  }
629
- /** Get AI-generated stock analysis report. Requires PRO tier. */
655
+ /**
656
+ * Get AI-generated stock analysis report. Requires PRO tier.
657
+ *
658
+ * `depth: "deep"` returns the full curated report and consumes one report view on
659
+ * metered tiers; the default `"basic"` returns the one-paragraph summary.
660
+ *
661
+ * The deprecated `forceRefresh` option is accepted and discarded, not forwarded.
662
+ */
630
663
  async getAISummary(ticker, options) {
631
- return this.client.get(`/api/v1/stocks/${encodeURIComponent(ticker)}/ai-summary`, options);
664
+ const { forceRefresh: _forceRefresh, ...params } = options ?? {};
665
+ return this.client.get(`/api/v1/stocks/${encodeURIComponent(ticker)}/ai-summary`, params);
632
666
  }
633
667
  /** Get sentiment/mention metrics breakdown by entity. */
634
668
  async getMetricsBreakdown(ticker, metricType, options) {
@@ -720,8 +754,8 @@ var Stocks = class {
720
754
  return this.client.get("/api/v1/stocks/with-kpis");
721
755
  }
722
756
  /**
723
- * List the KPI metadata tuples available for a ticker `id, name, category,
724
- * chartType` without paying the cost of the full series payload. Mirrors
757
+ * List the KPI metadata tuples available for a ticker (`id, name, category,
758
+ * chartType`) without paying the cost of the full series payload. Mirrors
725
759
  * the `/api/v1/insights/stock/{ticker}/types` precedent.
726
760
  *
727
761
  * Auth: API key required, no quota cost. 404 if the ticker has no curated KPIs.
@@ -733,13 +767,60 @@ var Stocks = class {
733
767
  }
734
768
  };
735
769
 
770
+ // src/resources/indexes.ts
771
+ var Indexes = class {
772
+ constructor(client) {
773
+ this.client = client;
774
+ }
775
+ /**
776
+ * List every index the platform publishes: id, display name, one-line
777
+ * description, the scale it lives on, its access tier, and where its richest
778
+ * view lives.
779
+ *
780
+ * Iterate this rather than hardcoding ids. Every `indexId` it advertises
781
+ * resolves on {@link get} and {@link history}.
782
+ */
783
+ async list() {
784
+ return this.client.get("/api/v1/indexes");
785
+ }
786
+ /**
787
+ * Latest reading for one index.
788
+ *
789
+ * Check `constituents` for `null` before iterating: it is `null` on a
790
+ * composite index like `market-mood`, which has no constituents by
791
+ * construction. For Market Mood this is the narrowed view; the phase band,
792
+ * weekly change, per-signal breakdown and per-sector map live on
793
+ * `client.marketMood.get()`, and both report the same headline number.
794
+ *
795
+ * @param indexId slug from {@link list}, e.g. `"fed-sentiment"`.
796
+ */
797
+ async get(indexId) {
798
+ return this.client.get(`/api/v1/indexes/${indexId}`);
799
+ }
800
+ /**
801
+ * Historical scalar series for one index, for charting.
802
+ *
803
+ * Thin or low-coverage buckets are withheld, so the series can be shorter
804
+ * than `days` and can contain gaps. Plot against each point's `date`.
805
+ *
806
+ * @param indexId slug from {@link list}.
807
+ * @param days days of history to return. Defaults to the API's own 180.
808
+ */
809
+ async history(indexId, days) {
810
+ return this.client.get(
811
+ `/api/v1/indexes/${indexId}/history`,
812
+ days === void 0 ? void 0 : { days }
813
+ );
814
+ }
815
+ };
816
+
736
817
  // src/resources/trackers.ts
737
818
  var Trackers = class {
738
819
  constructor(client) {
739
820
  this.client = client;
740
821
  }
741
822
  /**
742
- * List every publicly-visible tracker id, display name, category,
823
+ * List every publicly-visible tracker: id, display name, category,
743
824
  * one-line description, and the methodology anchor to link out to.
744
825
  */
745
826
  async list() {
@@ -753,8 +834,8 @@ var Trackers = class {
753
834
  * `"choropleth"` they live at `data.geo[]`; etc. Dispatch on `viewType`
754
835
  * in your renderer.
755
836
  *
756
- * @param trackerId slug from {@link list}, e.g. `"institution-concentration"`.
757
- * @param params provider-specific query params (e.g. `{ scope: "us" }` for
837
+ * @param trackerId slug from {@link list}, e.g. `"institution-concentration"`.
838
+ * @param params provider-specific query params (e.g. `{ scope: "us" }` for
758
839
  * geographically-scoped trackers like hantavirus). Unknown keys are ignored.
759
840
  */
760
841
  async get(trackerId, params) {
@@ -763,7 +844,7 @@ var Trackers = class {
763
844
  };
764
845
 
765
846
  // src/version.ts
766
- var VERSION = "0.33.0";
847
+ var VERSION = "0.35.0";
767
848
 
768
849
  // src/client.ts
769
850
  var DEFAULT_BASE_URL = "https://app.sentisense.ai";
@@ -775,12 +856,15 @@ var DEEP_HISTORY_FALLBACK_WAIT_S = 3;
775
856
  var MAX_DEEP_HISTORY_WAIT_S = 30;
776
857
  var MAX_RATE_LIMIT_WAIT_S = 120;
777
858
  var RATE_LIMIT_FALLBACK_WAIT_S = 60;
778
- function retryAfterSeconds(raw, defaultS, maxWaitS) {
779
- if (!raw) return defaultS;
859
+ function clampRetryAfter(raw, maxWaitS) {
860
+ if (!raw) return void 0;
780
861
  const parsed = Number(raw);
781
- if (!Number.isFinite(parsed)) return defaultS;
862
+ if (!Number.isFinite(parsed)) return void 0;
782
863
  return Math.min(Math.max(0.5, parsed), maxWaitS);
783
864
  }
865
+ function retryAfterSeconds(raw, defaultS, maxWaitS) {
866
+ return clampRetryAfter(raw, maxWaitS) ?? defaultS;
867
+ }
784
868
  function sleep(ms) {
785
869
  return new Promise((resolve) => setTimeout(resolve, ms));
786
870
  }
@@ -802,6 +886,7 @@ var SentiSense = class {
802
886
  this.marketMood = new MarketMoodResource(this);
803
887
  this.marketSummary = new MarketSummaryResource(this);
804
888
  this.kb = new KB(this);
889
+ this.indexes = new Indexes(this);
805
890
  this.trackers = new Trackers(this);
806
891
  this.calendar = new Calendar(this);
807
892
  }
@@ -949,8 +1034,11 @@ var SentiSense = class {
949
1034
  case 404:
950
1035
  throw new NotFoundError(message, code);
951
1036
  case 429: {
952
- const ra = response.headers.get("Retry-After");
953
- throw new RateLimitError(message, code, ra ? parseInt(ra, 10) : void 0);
1037
+ const retryAfter = clampRetryAfter(
1038
+ response.headers.get("Retry-After"),
1039
+ MAX_RATE_LIMIT_WAIT_S
1040
+ );
1041
+ throw new RateLimitError(message, code, retryAfter);
954
1042
  }
955
1043
  default:
956
1044
  throw new APIError(message, response.status, code);