sentisense 0.62.0 → 0.63.1
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 +28 -3
- package/dist/cli.cjs +37 -7
- package/dist/index.cjs +25 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +68 -9
- package/dist/index.d.ts +68 -9
- package/dist/index.mjs +25 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -157,7 +157,7 @@ const client = new SentiSense({
|
|
|
157
157
|
| `apiKey` | none | Sent as `X-SentiSense-API-Key`. Required by every endpoint. |
|
|
158
158
|
| `baseUrl` | `https://app.sentisense.ai` | Override for a non-production host. |
|
|
159
159
|
| `timeout` | `30000` | Per-request timeout in milliseconds. |
|
|
160
|
-
| `maxRetries` | `3` | Retries
|
|
160
|
+
| `maxRetries` | `3` | Retries the per-minute 429 and 5xx, honouring `Retry-After`. The monthly-allowance 429 is never retried. Set `0` to fail fast. |
|
|
161
161
|
| `userAgentSuffix` | none | Appended to the User-Agent, after `sentisense-node/{version}`. |
|
|
162
162
|
|
|
163
163
|
`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.
|
|
@@ -252,6 +252,8 @@ Price fields carry `priceAsOf` (Unix milliseconds) for the age of the market dat
|
|
|
252
252
|
|
|
253
253
|
Short interest and short volume come back newest first, and an unknown ticker returns an empty `dataPoints` array rather than an error. Every identifier in `getGraph` is a slug, the same handle the metric and document endpoints take. An ETF symbol is refused by `getQuote` with an `APIError` whose `code` is `"ticker_is_etf"`; use `etfs.quote()` for funds.
|
|
254
254
|
|
|
255
|
+
Product nodes (`type === "PRODUCT_OR_SERVICE"`) in `stocks.getGraph` now carry optional `category` when known. Other nodes and products without a category omit it.
|
|
256
|
+
|
|
255
257
|
### Documents & news
|
|
256
258
|
|
|
257
259
|
```typescript
|
|
@@ -292,6 +294,18 @@ client.institutional.getHolders("AAPL", "2025-02-14")
|
|
|
292
294
|
client.institutional.getActivists("2025-02-14")
|
|
293
295
|
```
|
|
294
296
|
|
|
297
|
+
`positionsHeld` on an institution detail counts the full portfolio's held positions:
|
|
298
|
+
`max(0, holdingsCount - soldOutPositions)`, even when the holdings list is truncated.
|
|
299
|
+
`getInstitutionDetail` keeps its `Promise<unknown>` signature. The exported all-optional
|
|
300
|
+
`InstitutionDetail` type is available for callers who want to opt in:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
import type { InstitutionDetail, PreviewResponse } from "sentisense";
|
|
304
|
+
|
|
305
|
+
const detail = await client.institutional.getInstitutionDetail("1067983") as PreviewResponse<InstitutionDetail>;
|
|
306
|
+
console.log(detail.data.positionsHeld); // undefined when an older response omits it
|
|
307
|
+
```
|
|
308
|
+
|
|
295
309
|
**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.
|
|
296
310
|
|
|
297
311
|
| Option | Values |
|
|
@@ -417,6 +431,8 @@ Two shapes to read rather than assume. A firm can appear with `noteCount: 0`, a
|
|
|
417
431
|
|
|
418
432
|
`ratingBuckets` sizes the same book by rating tier: `buy`, `hold`, `sell`, `unrated` and `total`, counted over every covering firm before the free truncation, so `buy + hold + sell + unrated === total` and a free key reads the same numbers as a PRO one. `unrated` is a desk with no current rating on record, such as a price-target-only firm. These count the firms in this coverage book, a different population from the `strongBuy` through `strongSell` figures on `client.analyst.consensus`, which come from the provider's analyst survey. Read one or the other, do not reconcile them.
|
|
419
433
|
|
|
434
|
+
Surprise rows now carry optional `surprisePct`, the signed true percent rounded half-up to two decimals: `(actualEps - estimateEps) * 100 / abs(estimateEps)`. It is `null` when either EPS is missing or the estimate is zero, and `undefined` on older responses. The existing `surprisePercent` fraction is unchanged (`0.03` means about 3%).
|
|
435
|
+
|
|
420
436
|
### Earnings
|
|
421
437
|
|
|
422
438
|
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. Summaries, recent reports, statistics, and rankings return the preview envelope; per-ticker reactions return a direct payload.
|
|
@@ -451,6 +467,8 @@ if (quarter) {
|
|
|
451
467
|
|
|
452
468
|
The forward-looking half of the family is `client.calendar.getEarnings()`, which covers scheduled dates and consensus EPS rather than results.
|
|
453
469
|
|
|
470
|
+
`earnings.getSummaries` exposes optional `totalCount` on the envelope on both PRO and FREE. It counts indexed quarters; returned rows can be fewer because of `limit` or an unavailable body. Older responses without the count still read as `undefined`.
|
|
471
|
+
|
|
454
472
|
### Company KPIs (PRO)
|
|
455
473
|
|
|
456
474
|
```typescript
|
|
@@ -511,6 +529,8 @@ During the trading session the dossier and the radar also carry a few intraday f
|
|
|
511
529
|
|
|
512
530
|
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 inside the usual envelope, so the check is `result.data === null`: the response object itself is always truthy, and a bare `if (summary === null)` never fires. `getOptionsHistory` reports it as an empty `series` instead, so check the array's length rather than null-checking there.
|
|
513
531
|
|
|
532
|
+
On a delisted symbol, the options summary carries `listingStatus === "DELISTED"` and, when known, `delistedDate` (YYYY-MM-DD). This marks a frozen last dossier, so read `asOf` before treating it as current. Listed and pending symbols omit both fields.
|
|
533
|
+
|
|
514
534
|
### SentiSense Rating
|
|
515
535
|
|
|
516
536
|
Where a stock ranks against the other stocks rated that day, as a score, a letter and a percentile, plus the seven dimensions the rank is blended from. It is a relative research signal for informational and educational purposes, not financial, investment or trading advice, and not a recommendation about any security. Every response carries the wording to display alongside a grade in `disclaimer`. [Methodology](https://sentisense.ai/methodology/#sentisense-rating).
|
|
@@ -616,7 +636,12 @@ try {
|
|
|
616
636
|
if (error instanceof AuthenticationError) {
|
|
617
637
|
// 401 or 403: invalid/missing API key or insufficient tier
|
|
618
638
|
} else if (error instanceof RateLimitError) {
|
|
619
|
-
|
|
639
|
+
if (error.code === "quota_exceeded") {
|
|
640
|
+
// 429: monthly allowance used up. Not retried; error.message says when it resets.
|
|
641
|
+
} else {
|
|
642
|
+
// 429: per-minute limit ("rate_limit_exceeded"), thrown once retries are used up.
|
|
643
|
+
// error.retryAfter is the server's wait in seconds.
|
|
644
|
+
}
|
|
620
645
|
}
|
|
621
646
|
}
|
|
622
647
|
```
|
|
@@ -625,7 +650,7 @@ try {
|
|
|
625
650
|
|------------|-------------|------|
|
|
626
651
|
| `AuthenticationError` | 401, 403 | Invalid API key or insufficient tier |
|
|
627
652
|
| `NotFoundError` | 404 | Resource not found |
|
|
628
|
-
| `RateLimitError` | 429 |
|
|
653
|
+
| `RateLimitError` | 429 | Per-minute limit (`code: "rate_limit_exceeded"`, retried first) or monthly allowance used up (`code: "quota_exceeded"`, thrown immediately) |
|
|
629
654
|
| `APIError` | Other 4xx/5xx | General API error |
|
|
630
655
|
|
|
631
656
|
All errors extend `SentiSenseError` and include `status`, `code`, and `message` properties.
|
package/dist/cli.cjs
CHANGED
|
@@ -66,10 +66,12 @@ var EXIT_TABLE = [
|
|
|
66
66
|
[EXIT.USAGE, "bad usage, caught before any request was sent"],
|
|
67
67
|
[EXIT.AUTH, "missing or rejected API key"],
|
|
68
68
|
[EXIT.NOT_FOUND, "no data for that symbol or identifier"],
|
|
69
|
-
[EXIT.RATE_LIMIT, "rate limited"],
|
|
69
|
+
[EXIT.RATE_LIMIT, "rate limited, or the monthly request allowance is used up"],
|
|
70
70
|
[EXIT.NETWORK, "network failure or timeout"]
|
|
71
71
|
];
|
|
72
72
|
var KEY_URL = "https://app.sentisense.ai/get-api-key";
|
|
73
|
+
var PRICING_URL = "https://app.sentisense.ai/pricing";
|
|
74
|
+
var QUOTA_EXCEEDED = "quota_exceeded";
|
|
73
75
|
var CliUsageError = class extends Error {
|
|
74
76
|
constructor(message, hint) {
|
|
75
77
|
super(message);
|
|
@@ -158,6 +160,15 @@ function classify(error, command) {
|
|
|
158
160
|
};
|
|
159
161
|
}
|
|
160
162
|
if (error instanceof RateLimitError) {
|
|
163
|
+
if (error.code === QUOTA_EXCEEDED) {
|
|
164
|
+
return {
|
|
165
|
+
exitCode: EXIT.RATE_LIMIT,
|
|
166
|
+
lines: [
|
|
167
|
+
`error: monthly request allowance used up: ${error.message}`,
|
|
168
|
+
`next: the free monthly allowance resets at the start of next month, so running it again before then will not help. PRO has no monthly cap: ${PRICING_URL}`
|
|
169
|
+
]
|
|
170
|
+
};
|
|
171
|
+
}
|
|
161
172
|
const wait = error.retryAfter ? Math.ceil(error.retryAfter) : 60;
|
|
162
173
|
return {
|
|
163
174
|
exitCode: EXIT.RATE_LIMIT,
|
|
@@ -1345,7 +1356,7 @@ var flowsCommand = {
|
|
|
1345
1356
|
};
|
|
1346
1357
|
|
|
1347
1358
|
// src/version.ts
|
|
1348
|
-
var VERSION = "0.
|
|
1359
|
+
var VERSION = "0.63.1";
|
|
1349
1360
|
|
|
1350
1361
|
// src/resources/analyst.ts
|
|
1351
1362
|
var Analyst = class {
|
|
@@ -1604,9 +1615,11 @@ var Earnings = class {
|
|
|
1604
1615
|
* management phrased it, and a summary of the earnings call.
|
|
1605
1616
|
*
|
|
1606
1617
|
* Branch on `isPreview`: a PRO key receives every hydrated quarter in full, a
|
|
1607
|
-
* FREE key receives the latest quarter shaped rather than truncated
|
|
1608
|
-
* `totalCount
|
|
1609
|
-
*
|
|
1618
|
+
* FREE key receives the latest quarter shaped rather than truncated.
|
|
1619
|
+
* `totalCount` on the envelope counts indexed quarters on both tiers. The
|
|
1620
|
+
* returned array can be shorter when a body is unavailable or limit cuts it.
|
|
1621
|
+
* Older responses may omit the count. {@link EarningsQuarter} documents
|
|
1622
|
+
* which fields each tier carries.
|
|
1610
1623
|
*
|
|
1611
1624
|
* A quarter typically appears within 48 hours of the company reporting, and
|
|
1612
1625
|
* the call summary can arrive after the press-release content for the same
|
|
@@ -2081,6 +2094,10 @@ var Institutional = class {
|
|
|
2081
2094
|
* Resolved by URL slug (e.g. `Berkshire-Hathaway`) or numeric SEC CIK.
|
|
2082
2095
|
* Free users receive the profile and top 10 holdings; PRO users receive the
|
|
2083
2096
|
* full holdings array. Returns 404 if the slug or CIK is unknown.
|
|
2097
|
+
*
|
|
2098
|
+
* The signature remains unknown for compatibility. Opt in to the exported
|
|
2099
|
+
* PreviewResponse<InstitutionDetail> type to read data.positionsHeld, the full
|
|
2100
|
+
* portfolio count excluding SOLD_OUT rows, even on a truncated holdings list.
|
|
2084
2101
|
*/
|
|
2085
2102
|
async getInstitutionDetail(slugOrCik) {
|
|
2086
2103
|
return this.client.get(
|
|
@@ -2353,8 +2370,9 @@ var Stocks = class {
|
|
|
2353
2370
|
*
|
|
2354
2371
|
* Every identifier in the response is a slug, the same handle the metric and document
|
|
2355
2372
|
* endpoints take, so a node can be passed straight to them. `depth` is 1 (default) or 2,
|
|
2356
|
-
* `cap` limits the
|
|
2357
|
-
* with a 400. A ticker with no
|
|
2373
|
+
* `cap` limits the number of non-root nodes (1 to 200, default 75); the root is always
|
|
2374
|
+
* included in addition. Out-of-range values are rejected with a 400. A ticker with no
|
|
2375
|
+
* graph returns a `NotFoundError`.
|
|
2358
2376
|
*/
|
|
2359
2377
|
async getGraph(ticker, options) {
|
|
2360
2378
|
return this.client.get(
|
|
@@ -2661,6 +2679,15 @@ var DEEP_HISTORY_FALLBACK_WAIT_S = 3;
|
|
|
2661
2679
|
var MAX_DEEP_HISTORY_WAIT_S = 30;
|
|
2662
2680
|
var MAX_RATE_LIMIT_WAIT_S = 120;
|
|
2663
2681
|
var RATE_LIMIT_FALLBACK_WAIT_S = 60;
|
|
2682
|
+
var QUOTA_EXCEEDED_CODE = "quota_exceeded";
|
|
2683
|
+
async function isQuotaExceeded(response) {
|
|
2684
|
+
try {
|
|
2685
|
+
const body = await response.clone().json();
|
|
2686
|
+
return body?.error === QUOTA_EXCEEDED_CODE;
|
|
2687
|
+
} catch {
|
|
2688
|
+
return false;
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2664
2691
|
function clampRetryAfter(raw, maxWaitS) {
|
|
2665
2692
|
if (!raw) return void 0;
|
|
2666
2693
|
const parsed = Number(raw);
|
|
@@ -2746,6 +2773,9 @@ var SentiSense = class {
|
|
|
2746
2773
|
);
|
|
2747
2774
|
}
|
|
2748
2775
|
if (!response.ok) {
|
|
2776
|
+
if (response.status === 429 && await isQuotaExceeded(response)) {
|
|
2777
|
+
await this.handleErrorResponse(response);
|
|
2778
|
+
}
|
|
2749
2779
|
const isRetryable = response.status === 429 || response.status >= 500;
|
|
2750
2780
|
if (isRetryable && attempt < this.maxRetries) {
|
|
2751
2781
|
if (response.status === 429 || response.status === 503) {
|
package/dist/index.cjs
CHANGED
|
@@ -339,9 +339,11 @@ var Earnings = class {
|
|
|
339
339
|
* management phrased it, and a summary of the earnings call.
|
|
340
340
|
*
|
|
341
341
|
* Branch on `isPreview`: a PRO key receives every hydrated quarter in full, a
|
|
342
|
-
* FREE key receives the latest quarter shaped rather than truncated
|
|
343
|
-
* `totalCount
|
|
344
|
-
*
|
|
342
|
+
* FREE key receives the latest quarter shaped rather than truncated.
|
|
343
|
+
* `totalCount` on the envelope counts indexed quarters on both tiers. The
|
|
344
|
+
* returned array can be shorter when a body is unavailable or limit cuts it.
|
|
345
|
+
* Older responses may omit the count. {@link EarningsQuarter} documents
|
|
346
|
+
* which fields each tier carries.
|
|
345
347
|
*
|
|
346
348
|
* A quarter typically appears within 48 hours of the company reporting, and
|
|
347
349
|
* the call summary can arrive after the press-release content for the same
|
|
@@ -816,6 +818,10 @@ var Institutional = class {
|
|
|
816
818
|
* Resolved by URL slug (e.g. `Berkshire-Hathaway`) or numeric SEC CIK.
|
|
817
819
|
* Free users receive the profile and top 10 holdings; PRO users receive the
|
|
818
820
|
* full holdings array. Returns 404 if the slug or CIK is unknown.
|
|
821
|
+
*
|
|
822
|
+
* The signature remains unknown for compatibility. Opt in to the exported
|
|
823
|
+
* PreviewResponse<InstitutionDetail> type to read data.positionsHeld, the full
|
|
824
|
+
* portfolio count excluding SOLD_OUT rows, even on a truncated holdings list.
|
|
819
825
|
*/
|
|
820
826
|
async getInstitutionDetail(slugOrCik) {
|
|
821
827
|
return this.client.get(
|
|
@@ -1088,8 +1094,9 @@ var Stocks = class {
|
|
|
1088
1094
|
*
|
|
1089
1095
|
* Every identifier in the response is a slug, the same handle the metric and document
|
|
1090
1096
|
* endpoints take, so a node can be passed straight to them. `depth` is 1 (default) or 2,
|
|
1091
|
-
* `cap` limits the
|
|
1092
|
-
* with a 400. A ticker with no
|
|
1097
|
+
* `cap` limits the number of non-root nodes (1 to 200, default 75); the root is always
|
|
1098
|
+
* included in addition. Out-of-range values are rejected with a 400. A ticker with no
|
|
1099
|
+
* graph returns a `NotFoundError`.
|
|
1093
1100
|
*/
|
|
1094
1101
|
async getGraph(ticker, options) {
|
|
1095
1102
|
return this.client.get(
|
|
@@ -1387,7 +1394,7 @@ var Trackers = class {
|
|
|
1387
1394
|
};
|
|
1388
1395
|
|
|
1389
1396
|
// src/version.ts
|
|
1390
|
-
var VERSION = "0.
|
|
1397
|
+
var VERSION = "0.63.1";
|
|
1391
1398
|
|
|
1392
1399
|
// src/client.ts
|
|
1393
1400
|
var DEFAULT_BASE_URL = "https://app.sentisense.ai";
|
|
@@ -1399,6 +1406,15 @@ var DEEP_HISTORY_FALLBACK_WAIT_S = 3;
|
|
|
1399
1406
|
var MAX_DEEP_HISTORY_WAIT_S = 30;
|
|
1400
1407
|
var MAX_RATE_LIMIT_WAIT_S = 120;
|
|
1401
1408
|
var RATE_LIMIT_FALLBACK_WAIT_S = 60;
|
|
1409
|
+
var QUOTA_EXCEEDED_CODE = "quota_exceeded";
|
|
1410
|
+
async function isQuotaExceeded(response) {
|
|
1411
|
+
try {
|
|
1412
|
+
const body = await response.clone().json();
|
|
1413
|
+
return body?.error === QUOTA_EXCEEDED_CODE;
|
|
1414
|
+
} catch {
|
|
1415
|
+
return false;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1402
1418
|
function clampRetryAfter(raw, maxWaitS) {
|
|
1403
1419
|
if (!raw) return void 0;
|
|
1404
1420
|
const parsed = Number(raw);
|
|
@@ -1484,6 +1500,9 @@ var SentiSense = class {
|
|
|
1484
1500
|
);
|
|
1485
1501
|
}
|
|
1486
1502
|
if (!response.ok) {
|
|
1503
|
+
if (response.status === 429 && await isQuotaExceeded(response)) {
|
|
1504
|
+
await this.handleErrorResponse(response);
|
|
1505
|
+
}
|
|
1487
1506
|
const isRetryable = response.status === 429 || response.status >= 500;
|
|
1488
1507
|
if (isRetryable && attempt < this.maxRetries) {
|
|
1489
1508
|
if (response.status === 429 || response.status === 503) {
|