form4api 0.7.1__tar.gz → 0.8.0__tar.gz
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.
- {form4api-0.7.1 → form4api-0.8.0}/PKG-INFO +12 -2
- {form4api-0.7.1 → form4api-0.8.0}/README.md +11 -1
- {form4api-0.7.1 → form4api-0.8.0}/form4api/__init__.py +4 -0
- {form4api-0.7.1 → form4api-0.8.0}/form4api/_generated.py +130 -38
- form4api-0.8.0/form4api/_types.py +157 -0
- {form4api-0.7.1 → form4api-0.8.0}/form4api/resources/_insiders.py +2 -2
- {form4api-0.7.1 → form4api-0.8.0}/form4api/resources/_transactions.py +37 -8
- {form4api-0.7.1 → form4api-0.8.0}/form4api.egg-info/PKG-INFO +12 -2
- {form4api-0.7.1 → form4api-0.8.0}/pyproject.toml +1 -1
- {form4api-0.7.1 → form4api-0.8.0}/tests/test_client.py +60 -0
- form4api-0.7.1/form4api/_types.py +0 -98
- {form4api-0.7.1 → form4api-0.8.0}/LICENSE +0 -0
- {form4api-0.7.1 → form4api-0.8.0}/form4api/_client.py +0 -0
- {form4api-0.7.1 → form4api-0.8.0}/form4api/_errors.py +0 -0
- {form4api-0.7.1 → form4api-0.8.0}/form4api/_webhook_utils.py +0 -0
- {form4api-0.7.1 → form4api-0.8.0}/form4api/resources/__init__.py +0 -0
- {form4api-0.7.1 → form4api-0.8.0}/form4api/resources/_companies.py +0 -0
- {form4api-0.7.1 → form4api-0.8.0}/form4api/resources/_signals.py +0 -0
- {form4api-0.7.1 → form4api-0.8.0}/form4api/resources/_webhooks.py +0 -0
- {form4api-0.7.1 → form4api-0.8.0}/form4api.egg-info/SOURCES.txt +0 -0
- {form4api-0.7.1 → form4api-0.8.0}/form4api.egg-info/dependency_links.txt +0 -0
- {form4api-0.7.1 → form4api-0.8.0}/form4api.egg-info/requires.txt +0 -0
- {form4api-0.7.1 → form4api-0.8.0}/form4api.egg-info/top_level.txt +0 -0
- {form4api-0.7.1 → form4api-0.8.0}/setup.cfg +0 -0
- {form4api-0.7.1 → form4api-0.8.0}/tests/test_generated.py +0 -0
- {form4api-0.7.1 → form4api-0.8.0}/tests/test_method_name.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: form4api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Python client for the Form4API — real-time SEC Form 4 insider trading, Form 144 intent-to-sell, institutional 13F-HR, and congressional STOCK Act trading data, including the insider/Congress convergence signal
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://www.form4api.com
|
|
@@ -108,16 +108,21 @@ For LLM workflows, `form4api-mcp` exposes the same endpoints as tools.
|
|
|
108
108
|
|
|
109
109
|
```python
|
|
110
110
|
client.transactions.list(
|
|
111
|
-
ticker="AAPL", # filter by ticker
|
|
111
|
+
ticker="AAPL", # filter by ticker — accepts up to 25 comma-separated symbols
|
|
112
112
|
cik="0000320193", # or by company CIK
|
|
113
113
|
insider_cik="...", # filter by insider CIK
|
|
114
114
|
code="P", # transaction code: P=purchase, S=sale, A=grant, etc.
|
|
115
115
|
from_date="2026-01-01",
|
|
116
116
|
to_date="2026-12-31",
|
|
117
|
+
filed_from="2026-01-01", # filters on Filing.FiledAt (when it hit EDGAR), not the trade date
|
|
118
|
+
filed_to="2026-12-31",
|
|
117
119
|
exclude_10b5=True, # omit trades filed under a Rule 10b5-1 plan
|
|
118
120
|
per_page=100,
|
|
119
121
|
page=1,
|
|
120
122
|
)
|
|
123
|
+
|
|
124
|
+
# Multi-ticker lookup in one call
|
|
125
|
+
client.transactions.list(ticker="AAPL,MSFT,NVDA")
|
|
121
126
|
```
|
|
122
127
|
|
|
123
128
|
### Granular filtering (v0.4.0+)
|
|
@@ -166,6 +171,11 @@ client.transactions.list(min_shares=10_000, max_shares=100_000)
|
|
|
166
171
|
| `is_derivative` | `bool` | Derivative security flag |
|
|
167
172
|
| `transaction_date` | `str` | ISO datetime |
|
|
168
173
|
| `period_of_report` | `str` | ISO datetime |
|
|
174
|
+
| `return1d`/`return1w`/`return1m`/`return3m`/`return6m` | `float \| None` | Forward return since the transaction date, as a fraction (0.109 = +10.9%); `None` until enough time/price data has elapsed |
|
|
175
|
+
| `value_quality` | `str \| None` | Confidence flag on `total_value`'s pricing source |
|
|
176
|
+
| `accepted_at` | `str \| None` | Precise UTC SEC-acceptance instant; `None` if not captured |
|
|
177
|
+
| `document_url` | `str \| None` | Public SEC document URL |
|
|
178
|
+
| `institutional_ownership` | `InstitutionalOwnership \| None` | Latest 13F institutional ownership snapshot for the ticker, when available |
|
|
169
179
|
|
|
170
180
|
### Company fields
|
|
171
181
|
|
|
@@ -89,16 +89,21 @@ For LLM workflows, `form4api-mcp` exposes the same endpoints as tools.
|
|
|
89
89
|
|
|
90
90
|
```python
|
|
91
91
|
client.transactions.list(
|
|
92
|
-
ticker="AAPL", # filter by ticker
|
|
92
|
+
ticker="AAPL", # filter by ticker — accepts up to 25 comma-separated symbols
|
|
93
93
|
cik="0000320193", # or by company CIK
|
|
94
94
|
insider_cik="...", # filter by insider CIK
|
|
95
95
|
code="P", # transaction code: P=purchase, S=sale, A=grant, etc.
|
|
96
96
|
from_date="2026-01-01",
|
|
97
97
|
to_date="2026-12-31",
|
|
98
|
+
filed_from="2026-01-01", # filters on Filing.FiledAt (when it hit EDGAR), not the trade date
|
|
99
|
+
filed_to="2026-12-31",
|
|
98
100
|
exclude_10b5=True, # omit trades filed under a Rule 10b5-1 plan
|
|
99
101
|
per_page=100,
|
|
100
102
|
page=1,
|
|
101
103
|
)
|
|
104
|
+
|
|
105
|
+
# Multi-ticker lookup in one call
|
|
106
|
+
client.transactions.list(ticker="AAPL,MSFT,NVDA")
|
|
102
107
|
```
|
|
103
108
|
|
|
104
109
|
### Granular filtering (v0.4.0+)
|
|
@@ -147,6 +152,11 @@ client.transactions.list(min_shares=10_000, max_shares=100_000)
|
|
|
147
152
|
| `is_derivative` | `bool` | Derivative security flag |
|
|
148
153
|
| `transaction_date` | `str` | ISO datetime |
|
|
149
154
|
| `period_of_report` | `str` | ISO datetime |
|
|
155
|
+
| `return1d`/`return1w`/`return1m`/`return3m`/`return6m` | `float \| None` | Forward return since the transaction date, as a fraction (0.109 = +10.9%); `None` until enough time/price data has elapsed |
|
|
156
|
+
| `value_quality` | `str \| None` | Confidence flag on `total_value`'s pricing source |
|
|
157
|
+
| `accepted_at` | `str \| None` | Precise UTC SEC-acceptance instant; `None` if not captured |
|
|
158
|
+
| `document_url` | `str \| None` | Public SEC document URL |
|
|
159
|
+
| `institutional_ownership` | `InstitutionalOwnership \| None` | Latest 13F institutional ownership snapshot for the ticker, when available |
|
|
150
160
|
|
|
151
161
|
### Company fields
|
|
152
162
|
|
|
@@ -11,6 +11,8 @@ from form4api._types import (
|
|
|
11
11
|
Company,
|
|
12
12
|
Insider,
|
|
13
13
|
InsiderSignal,
|
|
14
|
+
InstitutionalOwnership,
|
|
15
|
+
TopHolder,
|
|
14
16
|
Transaction,
|
|
15
17
|
WebhookCreated,
|
|
16
18
|
WebhookEvent,
|
|
@@ -31,6 +33,8 @@ __all__ = [
|
|
|
31
33
|
"Insider",
|
|
32
34
|
"Company",
|
|
33
35
|
"InsiderSignal",
|
|
36
|
+
"InstitutionalOwnership",
|
|
37
|
+
"TopHolder",
|
|
34
38
|
"WebhookCreated",
|
|
35
39
|
"WebhookEvent",
|
|
36
40
|
"WebhookSubscription",
|
|
@@ -468,6 +468,47 @@ class DirectoryLetter:
|
|
|
468
468
|
return cls(**{k: v for k, v in data.items() if k in known})
|
|
469
469
|
|
|
470
470
|
|
|
471
|
+
@dataclass
|
|
472
|
+
class ErrorDetail:
|
|
473
|
+
code: str | None = None
|
|
474
|
+
current_plan: str | None = None
|
|
475
|
+
message: str | None = None
|
|
476
|
+
reason: str | None = None
|
|
477
|
+
request_id: str | None = None
|
|
478
|
+
required_plan: str | None = None
|
|
479
|
+
upgrade_url: str | None = None
|
|
480
|
+
|
|
481
|
+
@classmethod
|
|
482
|
+
def _from_dict(cls, data: dict) -> "ErrorDetail":
|
|
483
|
+
"""Build from an API payload, ignoring unknown keys.
|
|
484
|
+
|
|
485
|
+
Constructing with **data directly (as the hand-written resources do)
|
|
486
|
+
means the SDK raises TypeError the moment the backend adds a field.
|
|
487
|
+
Filtering keeps older SDK versions working against a newer API."""
|
|
488
|
+
known = {f.name for f in fields(cls)}
|
|
489
|
+
return cls(**{k: v for k, v in data.items() if k in known})
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
@dataclass
|
|
493
|
+
class ErrorResponse:
|
|
494
|
+
"""Standard error envelope returned by every non-2xx response."""
|
|
495
|
+
|
|
496
|
+
error: ErrorDetail | None = None
|
|
497
|
+
|
|
498
|
+
@classmethod
|
|
499
|
+
def _from_dict(cls, data: dict) -> "ErrorResponse":
|
|
500
|
+
"""Build from an API payload, ignoring unknown keys.
|
|
501
|
+
|
|
502
|
+
Constructing with **data directly (as the hand-written resources do)
|
|
503
|
+
means the SDK raises TypeError the moment the backend adds a field.
|
|
504
|
+
Filtering keeps older SDK versions working against a newer API."""
|
|
505
|
+
known = {f.name for f in fields(cls)}
|
|
506
|
+
kwargs = {k: v for k, v in data.items() if k in known}
|
|
507
|
+
if isinstance(kwargs.get("error"), dict):
|
|
508
|
+
kwargs["error"] = ErrorDetail._from_dict(kwargs["error"])
|
|
509
|
+
return cls(**kwargs)
|
|
510
|
+
|
|
511
|
+
|
|
471
512
|
@dataclass
|
|
472
513
|
class ExcludedTradeEntry:
|
|
473
514
|
code: str | None = None
|
|
@@ -490,10 +531,12 @@ class ExcludedTradeEntry:
|
|
|
490
531
|
|
|
491
532
|
@dataclass
|
|
492
533
|
class FilingResponse:
|
|
534
|
+
accepted_at: str | None = None
|
|
493
535
|
accession_number: str | None = None
|
|
494
536
|
amendment_type: str | None = None
|
|
495
537
|
company_name: str | None = None
|
|
496
538
|
company_ticker: str | None = None
|
|
539
|
+
document_url: str | None = None
|
|
497
540
|
filed_at: str | None = None
|
|
498
541
|
period_of_report: str | None = None
|
|
499
542
|
transaction_count: int | None = None
|
|
@@ -517,13 +560,16 @@ class Form144Response:
|
|
|
517
560
|
approx_sale_date: str | None = None
|
|
518
561
|
broker: str | None = None
|
|
519
562
|
company_name: str | None = None
|
|
563
|
+
document_url: str | None = None
|
|
520
564
|
exchange: str | None = None
|
|
521
565
|
filed_at: str | None = None
|
|
566
|
+
filer_cik: str | None = None
|
|
522
567
|
insider_name: str | None = None
|
|
523
568
|
is_under10b5_plan: bool | None = None
|
|
524
569
|
nature_of_acquisition: str | None = None
|
|
525
570
|
notice_date: str | None = None
|
|
526
571
|
relationship: str | None = None
|
|
572
|
+
securities_class_title: str | None = None
|
|
527
573
|
shares_proposed: float | None = None
|
|
528
574
|
ticker: str | None = None
|
|
529
575
|
|
|
@@ -1019,6 +1065,32 @@ class ReturnsCoverage:
|
|
|
1019
1065
|
return cls(**{k: v for k, v in data.items() if k in known})
|
|
1020
1066
|
|
|
1021
1067
|
|
|
1068
|
+
@dataclass
|
|
1069
|
+
class Schedule13DGResponse:
|
|
1070
|
+
accession_number: str | None = None
|
|
1071
|
+
company_name: str | None = None
|
|
1072
|
+
event_date: str | None = None
|
|
1073
|
+
filed_at: str | None = None
|
|
1074
|
+
filer_cik: str | None = None
|
|
1075
|
+
filer_name: str | None = None
|
|
1076
|
+
form_type: str | None = None
|
|
1077
|
+
is_amendment: bool | None = None
|
|
1078
|
+
ownership_percent: float | None = None
|
|
1079
|
+
reporting_person_type: str | None = None
|
|
1080
|
+
shares_owned: float | None = None
|
|
1081
|
+
ticker: str | None = None
|
|
1082
|
+
|
|
1083
|
+
@classmethod
|
|
1084
|
+
def _from_dict(cls, data: dict) -> "Schedule13DGResponse":
|
|
1085
|
+
"""Build from an API payload, ignoring unknown keys.
|
|
1086
|
+
|
|
1087
|
+
Constructing with **data directly (as the hand-written resources do)
|
|
1088
|
+
means the SDK raises TypeError the moment the backend adds a field.
|
|
1089
|
+
Filtering keeps older SDK versions working against a newer API."""
|
|
1090
|
+
known = {f.name for f in fields(cls)}
|
|
1091
|
+
return cls(**{k: v for k, v in data.items() if k in known})
|
|
1092
|
+
|
|
1093
|
+
|
|
1022
1094
|
@dataclass
|
|
1023
1095
|
class ScorecardTradeRef:
|
|
1024
1096
|
filed_at: str | None = None
|
|
@@ -1195,9 +1267,11 @@ class TopHolderDto:
|
|
|
1195
1267
|
|
|
1196
1268
|
@dataclass
|
|
1197
1269
|
class TransactionResponse:
|
|
1270
|
+
accepted_at: str | None = None
|
|
1198
1271
|
accession_number: str | None = None
|
|
1199
1272
|
company_name: str | None = None
|
|
1200
1273
|
direct_indirect: str | None = None
|
|
1274
|
+
document_url: str | None = None
|
|
1201
1275
|
insider_cik: str | None = None
|
|
1202
1276
|
insider_name: str | None = None
|
|
1203
1277
|
insider_title: str | None = None
|
|
@@ -1344,13 +1418,14 @@ class GeneratedCongressResource:
|
|
|
1344
1418
|
data = self._client._get(f"/v1/congress/politicians/{id_or_slug}", params=params)
|
|
1345
1419
|
return CongressPoliticianProfileResponse._from_dict(data)
|
|
1346
1420
|
|
|
1347
|
-
def politicians(self, *, page: int | None = None, per_page: int | None = None) -> list[CongressPoliticianRollupDto]:
|
|
1421
|
+
def politicians(self, *, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[CongressPoliticianRollupDto]:
|
|
1348
1422
|
"""Ranked rollup of politicians by congressional trade activity (Pro plan+)
|
|
1349
1423
|
|
|
1350
|
-
Returns a paginated list of politicians who have at least one non-superseded congressional trade, each with total/buy/sell counts (sells include both Sale and PartialSale; Exchange trades count only toward total) and their most recent trade's disclosure date. Ordered by total trade count descending, ties broken by most recently disclosed. Use this to discover active traders; for one politician's full profile (including their most-traded tickers and recent trades) use GET /v1/congress/politicians/{idOrSlug}. Requires Pro plan or higher (402 PLAN_REQUIRED on Free/Starter). Query runs live — no caching."""
|
|
1424
|
+
Returns a paginated list of politicians who have at least one non-superseded congressional trade, each with total/buy/sell counts (sells include both Sale and PartialSale; Exchange trades count only toward total) and their most recent trade's disclosure date. Ordered by total trade count descending, ties broken by most recently disclosed. Use this to discover active traders; for one politician's full profile (including their most-traded tickers and recent trades) use GET /v1/congress/politicians/{idOrSlug}. Requires Pro plan or higher (402 PLAN_REQUIRED on Free/Starter). `limit` is accepted as an alias for `per_page`. Query runs live — no caching."""
|
|
1351
1425
|
params = {
|
|
1352
1426
|
"page": page,
|
|
1353
1427
|
"per_page": per_page,
|
|
1428
|
+
"limit": limit,
|
|
1354
1429
|
}
|
|
1355
1430
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1356
1431
|
data = self._client._get(f"/v1/congress/politicians", params=params)
|
|
@@ -1367,10 +1442,10 @@ class GeneratedCongressResource:
|
|
|
1367
1442
|
data = self._client._get(f"/v1/congress/tickers/{ticker}", params=params)
|
|
1368
1443
|
return CongressTickerRollupResponse._from_dict(data)
|
|
1369
1444
|
|
|
1370
|
-
def trades(self, *, ticker: str | None = None, politician: str | None = None, party: str | None = None, chamber: str | None = None, state: str | None = None, transaction_type: str | None = None, min_amount: float | None = None, transaction_date_from: str | None = None, transaction_date_to: str | None = None, disclosure_date_from: str | None = None, disclosure_date_to: str | None = None, page: int | None = None, per_page: int | None = None) -> list[CongressTradeDto]:
|
|
1445
|
+
def trades(self, *, ticker: str | None = None, politician: str | None = None, party: str | None = None, chamber: str | None = None, state: str | None = None, transaction_type: str | None = None, min_amount: float | None = None, transaction_date_from: str | None = None, transaction_date_to: str | None = None, disclosure_date_from: str | None = None, disclosure_date_to: str | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[CongressTradeDto]:
|
|
1371
1446
|
"""Query congressional STOCK Act trades (Free+, plan-clamped disclosure window)
|
|
1372
1447
|
|
|
1373
|
-
Returns a paginated JSON list of congressional periodic-transaction-report trades, most recently DISCLOSED first, with non-superseded rows only (amended-away rows never appear). COVERAGE — HOUSE ONLY TODAY: every trade in this dataset comes from the U.S. House Clerk's PTR index. Senate eFD (efdsearch.senate.gov) returns 403 to datacenter traffic, so no Senate filings are ingested yet. chamber=Senate remains a valid filter but matches nothing and returns the response header X-Coverage-Note: chamber-not-covered, so an empty result is never ambiguous. Scanning by chamber should treat that header as "not covered", not as "no trades". PLAN-CLAMPED WINDOW: this endpoint is open to every plan, but how far back you can see is clamped on disclosureDate — Free sees only trades disclosed in the last 30 days, Starter the last 366 days, Pro/Business/Enterprise unlimited history. Passing an older disclosure_date_from than your plan allows does not extend the window — the floor always wins. Filters: ticker, politician (bioguideId, exact), party (free-text, case-insensitive exact match — not a fixed enum), chamber (House|Senate — see the coverage note above), state (2-letter code), transaction_type (purchase|sale|partial_sale|exchange), min_amount (range-aware — matches AmountLow >= value, never a fabricated midpoint), transaction_date_from/to, disclosure_date_from/to. Every row always carries BOTH amountLow and amountHigh (STOCK Act discloses ranges, never exact figures) and disclosureLagDays = (disclosureDate - transactionDate) — the STOCK Act allows up to 45 days of lag, so "real-time" here means minutes-after-disclosure, not minutes-after-trade. For per-politician or per-ticker rollups use GET /v1/congress/politicians, /v1/congress/politicians/{idOrSlug}, or /v1/congress/tickers/{ticker} (all Pro+). Query runs live against the database — no caching."""
|
|
1448
|
+
Returns a paginated JSON list of congressional periodic-transaction-report trades, most recently DISCLOSED first, with non-superseded rows only (amended-away rows never appear). COVERAGE — HOUSE ONLY TODAY: every trade in this dataset comes from the U.S. House Clerk's PTR index. Senate eFD (efdsearch.senate.gov) returns 403 to datacenter traffic, so no Senate filings are ingested yet. chamber=Senate remains a valid filter but matches nothing and returns the response header X-Coverage-Note: chamber-not-covered, so an empty result is never ambiguous. Scanning by chamber should treat that header as "not covered", not as "no trades". PLAN-CLAMPED WINDOW: this endpoint is open to every plan, but how far back you can see is clamped on disclosureDate — Free sees only trades disclosed in the last 30 days, Starter the last 366 days, Pro/Business/Enterprise unlimited history. Passing an older disclosure_date_from than your plan allows does not extend the window — the floor always wins. Filters: ticker, politician (bioguideId, exact), party (free-text, case-insensitive exact match — not a fixed enum), chamber (House|Senate — see the coverage note above), state (2-letter code), transaction_type (purchase|sale|partial_sale|exchange), min_amount (range-aware — matches AmountLow >= value, never a fabricated midpoint), transaction_date_from/to, disclosure_date_from/to. Every row always carries BOTH amountLow and amountHigh (STOCK Act discloses ranges, never exact figures) and disclosureLagDays = (disclosureDate - transactionDate) — the STOCK Act allows up to 45 days of lag, so "real-time" here means minutes-after-disclosure, not minutes-after-trade. For per-politician or per-ticker rollups use GET /v1/congress/politicians, /v1/congress/politicians/{idOrSlug}, or /v1/congress/tickers/{ticker} (all Pro+). To check whether an insider cluster-buy lines up with a congressional purchase in the same ticker, use GET /v1/signals/convergence (Pro+); for the company's own profile use GET /v1/companies/{ticker} (Free). `limit` is accepted as an alias for `per_page`. Query runs live against the database — no caching."""
|
|
1374
1449
|
params = {
|
|
1375
1450
|
"ticker": ticker,
|
|
1376
1451
|
"politician": politician,
|
|
@@ -1385,6 +1460,7 @@ class GeneratedCongressResource:
|
|
|
1385
1460
|
"disclosure_date_to": disclosure_date_to,
|
|
1386
1461
|
"page": page,
|
|
1387
1462
|
"per_page": per_page,
|
|
1463
|
+
"limit": limit,
|
|
1388
1464
|
}
|
|
1389
1465
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1390
1466
|
data = self._client._get(f"/v1/congress/trades", params=params)
|
|
@@ -1407,13 +1483,14 @@ class GeneratedAsyncCongressResource:
|
|
|
1407
1483
|
data = await self._client._get(f"/v1/congress/politicians/{id_or_slug}", params=params)
|
|
1408
1484
|
return CongressPoliticianProfileResponse._from_dict(data)
|
|
1409
1485
|
|
|
1410
|
-
async def politicians(self, *, page: int | None = None, per_page: int | None = None) -> list[CongressPoliticianRollupDto]:
|
|
1486
|
+
async def politicians(self, *, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[CongressPoliticianRollupDto]:
|
|
1411
1487
|
"""Ranked rollup of politicians by congressional trade activity (Pro plan+)
|
|
1412
1488
|
|
|
1413
|
-
Returns a paginated list of politicians who have at least one non-superseded congressional trade, each with total/buy/sell counts (sells include both Sale and PartialSale; Exchange trades count only toward total) and their most recent trade's disclosure date. Ordered by total trade count descending, ties broken by most recently disclosed. Use this to discover active traders; for one politician's full profile (including their most-traded tickers and recent trades) use GET /v1/congress/politicians/{idOrSlug}. Requires Pro plan or higher (402 PLAN_REQUIRED on Free/Starter). Query runs live — no caching."""
|
|
1489
|
+
Returns a paginated list of politicians who have at least one non-superseded congressional trade, each with total/buy/sell counts (sells include both Sale and PartialSale; Exchange trades count only toward total) and their most recent trade's disclosure date. Ordered by total trade count descending, ties broken by most recently disclosed. Use this to discover active traders; for one politician's full profile (including their most-traded tickers and recent trades) use GET /v1/congress/politicians/{idOrSlug}. Requires Pro plan or higher (402 PLAN_REQUIRED on Free/Starter). `limit` is accepted as an alias for `per_page`. Query runs live — no caching."""
|
|
1414
1490
|
params = {
|
|
1415
1491
|
"page": page,
|
|
1416
1492
|
"per_page": per_page,
|
|
1493
|
+
"limit": limit,
|
|
1417
1494
|
}
|
|
1418
1495
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1419
1496
|
data = await self._client._get(f"/v1/congress/politicians", params=params)
|
|
@@ -1430,10 +1507,10 @@ class GeneratedAsyncCongressResource:
|
|
|
1430
1507
|
data = await self._client._get(f"/v1/congress/tickers/{ticker}", params=params)
|
|
1431
1508
|
return CongressTickerRollupResponse._from_dict(data)
|
|
1432
1509
|
|
|
1433
|
-
async def trades(self, *, ticker: str | None = None, politician: str | None = None, party: str | None = None, chamber: str | None = None, state: str | None = None, transaction_type: str | None = None, min_amount: float | None = None, transaction_date_from: str | None = None, transaction_date_to: str | None = None, disclosure_date_from: str | None = None, disclosure_date_to: str | None = None, page: int | None = None, per_page: int | None = None) -> list[CongressTradeDto]:
|
|
1510
|
+
async def trades(self, *, ticker: str | None = None, politician: str | None = None, party: str | None = None, chamber: str | None = None, state: str | None = None, transaction_type: str | None = None, min_amount: float | None = None, transaction_date_from: str | None = None, transaction_date_to: str | None = None, disclosure_date_from: str | None = None, disclosure_date_to: str | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[CongressTradeDto]:
|
|
1434
1511
|
"""Query congressional STOCK Act trades (Free+, plan-clamped disclosure window)
|
|
1435
1512
|
|
|
1436
|
-
Returns a paginated JSON list of congressional periodic-transaction-report trades, most recently DISCLOSED first, with non-superseded rows only (amended-away rows never appear). COVERAGE — HOUSE ONLY TODAY: every trade in this dataset comes from the U.S. House Clerk's PTR index. Senate eFD (efdsearch.senate.gov) returns 403 to datacenter traffic, so no Senate filings are ingested yet. chamber=Senate remains a valid filter but matches nothing and returns the response header X-Coverage-Note: chamber-not-covered, so an empty result is never ambiguous. Scanning by chamber should treat that header as "not covered", not as "no trades". PLAN-CLAMPED WINDOW: this endpoint is open to every plan, but how far back you can see is clamped on disclosureDate — Free sees only trades disclosed in the last 30 days, Starter the last 366 days, Pro/Business/Enterprise unlimited history. Passing an older disclosure_date_from than your plan allows does not extend the window — the floor always wins. Filters: ticker, politician (bioguideId, exact), party (free-text, case-insensitive exact match — not a fixed enum), chamber (House|Senate — see the coverage note above), state (2-letter code), transaction_type (purchase|sale|partial_sale|exchange), min_amount (range-aware — matches AmountLow >= value, never a fabricated midpoint), transaction_date_from/to, disclosure_date_from/to. Every row always carries BOTH amountLow and amountHigh (STOCK Act discloses ranges, never exact figures) and disclosureLagDays = (disclosureDate - transactionDate) — the STOCK Act allows up to 45 days of lag, so "real-time" here means minutes-after-disclosure, not minutes-after-trade. For per-politician or per-ticker rollups use GET /v1/congress/politicians, /v1/congress/politicians/{idOrSlug}, or /v1/congress/tickers/{ticker} (all Pro+). Query runs live against the database — no caching."""
|
|
1513
|
+
Returns a paginated JSON list of congressional periodic-transaction-report trades, most recently DISCLOSED first, with non-superseded rows only (amended-away rows never appear). COVERAGE — HOUSE ONLY TODAY: every trade in this dataset comes from the U.S. House Clerk's PTR index. Senate eFD (efdsearch.senate.gov) returns 403 to datacenter traffic, so no Senate filings are ingested yet. chamber=Senate remains a valid filter but matches nothing and returns the response header X-Coverage-Note: chamber-not-covered, so an empty result is never ambiguous. Scanning by chamber should treat that header as "not covered", not as "no trades". PLAN-CLAMPED WINDOW: this endpoint is open to every plan, but how far back you can see is clamped on disclosureDate — Free sees only trades disclosed in the last 30 days, Starter the last 366 days, Pro/Business/Enterprise unlimited history. Passing an older disclosure_date_from than your plan allows does not extend the window — the floor always wins. Filters: ticker, politician (bioguideId, exact), party (free-text, case-insensitive exact match — not a fixed enum), chamber (House|Senate — see the coverage note above), state (2-letter code), transaction_type (purchase|sale|partial_sale|exchange), min_amount (range-aware — matches AmountLow >= value, never a fabricated midpoint), transaction_date_from/to, disclosure_date_from/to. Every row always carries BOTH amountLow and amountHigh (STOCK Act discloses ranges, never exact figures) and disclosureLagDays = (disclosureDate - transactionDate) — the STOCK Act allows up to 45 days of lag, so "real-time" here means minutes-after-disclosure, not minutes-after-trade. For per-politician or per-ticker rollups use GET /v1/congress/politicians, /v1/congress/politicians/{idOrSlug}, or /v1/congress/tickers/{ticker} (all Pro+). To check whether an insider cluster-buy lines up with a congressional purchase in the same ticker, use GET /v1/signals/convergence (Pro+); for the company's own profile use GET /v1/companies/{ticker} (Free). `limit` is accepted as an alias for `per_page`. Query runs live against the database — no caching."""
|
|
1437
1514
|
params = {
|
|
1438
1515
|
"ticker": ticker,
|
|
1439
1516
|
"politician": politician,
|
|
@@ -1448,6 +1525,7 @@ class GeneratedAsyncCongressResource:
|
|
|
1448
1525
|
"disclosure_date_to": disclosure_date_to,
|
|
1449
1526
|
"page": page,
|
|
1450
1527
|
"per_page": per_page,
|
|
1528
|
+
"limit": limit,
|
|
1451
1529
|
}
|
|
1452
1530
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1453
1531
|
data = await self._client._get(f"/v1/congress/trades", params=params)
|
|
@@ -1485,14 +1563,14 @@ class GeneratedFilingsResource:
|
|
|
1485
1563
|
def get(self, accession: str) -> FilingResponse:
|
|
1486
1564
|
"""Get a single Form 4 filing by its exact SEC accession number
|
|
1487
1565
|
|
|
1488
|
-
Returns one filing's metadata — accession number, company ticker/name, period of report, filed date, amendment type (Original/Amendment), and the count of non-superseded transactions it contains. Use this to look up a specific filing you already have the accession number for (e.g. from GET /v1/filings/recent or GET /v1/transactions); it does not return the individual transaction rows themselves — pull those via GET /v1/transactions filtered by ticker/cik and date. Returns 404 NOT_FOUND if the accession number isn't tracked. Not plan-gated. Query runs live against the database — no caching."""
|
|
1566
|
+
Returns one filing's metadata — accession number, company ticker/name, period of report, filed date, acceptedAt (precise UTC SEC-acceptance instant, nullable), documentUrl, amendment type (Original/Amendment), and the count of non-superseded transactions it contains. Use this to look up a specific filing you already have the accession number for (e.g. from GET /v1/filings/recent or GET /v1/transactions); it does not return the individual transaction rows themselves — pull those via GET /v1/transactions filtered by ticker/cik and date. Returns 404 NOT_FOUND if the accession number isn't tracked. Not plan-gated. Query runs live against the database — no caching."""
|
|
1489
1567
|
data = self._client._get(f"/v1/filings/{accession}")
|
|
1490
1568
|
return FilingResponse._from_dict(data)
|
|
1491
1569
|
|
|
1492
1570
|
def list(self, *, ticker: str | None = None, cik: str | None = None, from_: str | None = None, to: str | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[FilingResponse]:
|
|
1493
1571
|
"""List Form 4 filings with optional ticker, CIK and date filters
|
|
1494
1572
|
|
|
1495
|
-
Returns a paginated list of Form 4 filings, newest filed first. Filter by ticker, cik, and a from/to filed-date window. Each entry carries the accession number, company ticker/name, period of report, filed date, amendment type (Original/Amendment), and the count of non-superseded transactions in that filing. Use this for a company's filing HISTORY; use GET /v1/filings/recent for a live newest-first feed (it has no page parameter), and GET /v1/transactions when you want the individual trades rather than the filings that contain them. `limit` is accepted as an alias for `per_page`. Not plan-gated."""
|
|
1573
|
+
Returns a paginated list of Form 4 filings, newest filed first. Filter by ticker, cik, and a from/to filed-date window. Each entry carries the accession number, company ticker/name, period of report, filed date, acceptedAt (the precise UTC SEC-acceptance instant, null if not captured), documentUrl (the public SEC document URL), amendment type (Original/Amendment), and the count of non-superseded transactions in that filing. Use this for a company's filing HISTORY; use GET /v1/filings/recent for a live newest-first feed (it has no page parameter), and GET /v1/transactions when you want the individual trades rather than the filings that contain them. `limit` is accepted as an alias for `per_page`. Not plan-gated."""
|
|
1496
1574
|
params = {
|
|
1497
1575
|
"ticker": ticker,
|
|
1498
1576
|
"cik": cik,
|
|
@@ -1509,7 +1587,7 @@ class GeneratedFilingsResource:
|
|
|
1509
1587
|
def recent(self, *, ticker: str | None = None, per_page: int | None = None) -> list[FilingResponse]:
|
|
1510
1588
|
"""Get the most recently filed Form 4s, optionally filtered by ticker
|
|
1511
1589
|
|
|
1512
|
-
Returns the most recently filed Form 4s across all companies, newest first, optionally restricted to a single ticker. Use this to monitor new insider activity as it's ingested (e.g. a live "latest filings" feed) rather than for historical or bulk queries — for date-range or filter-heavy queries use GET /v1/transactions with from/to instead. Each entry includes the accession number, company ticker/name, period of report, filed date, amendment type, and the count of non-superseded transactions in that filing. There is no page parameter — this always returns the newest per_page filings, not an arbitrary offset. Not plan-gated."""
|
|
1590
|
+
Returns the most recently filed Form 4s across all companies, newest first, optionally restricted to a single ticker. Use this to monitor new insider activity as it's ingested (e.g. a live "latest filings" feed) rather than for historical or bulk queries — for date-range or filter-heavy queries use GET /v1/transactions with from/to instead. Each entry includes the accession number, company ticker/name, period of report, filed date, acceptedAt (precise UTC SEC-acceptance instant, nullable), documentUrl, amendment type, and the count of non-superseded transactions in that filing. There is no page parameter — this always returns the newest per_page filings, not an arbitrary offset. Not plan-gated."""
|
|
1513
1591
|
params = {
|
|
1514
1592
|
"ticker": ticker,
|
|
1515
1593
|
"per_page": per_page,
|
|
@@ -1526,14 +1604,14 @@ class GeneratedAsyncFilingsResource:
|
|
|
1526
1604
|
async def get(self, accession: str) -> FilingResponse:
|
|
1527
1605
|
"""Get a single Form 4 filing by its exact SEC accession number
|
|
1528
1606
|
|
|
1529
|
-
Returns one filing's metadata — accession number, company ticker/name, period of report, filed date, amendment type (Original/Amendment), and the count of non-superseded transactions it contains. Use this to look up a specific filing you already have the accession number for (e.g. from GET /v1/filings/recent or GET /v1/transactions); it does not return the individual transaction rows themselves — pull those via GET /v1/transactions filtered by ticker/cik and date. Returns 404 NOT_FOUND if the accession number isn't tracked. Not plan-gated. Query runs live against the database — no caching."""
|
|
1607
|
+
Returns one filing's metadata — accession number, company ticker/name, period of report, filed date, acceptedAt (precise UTC SEC-acceptance instant, nullable), documentUrl, amendment type (Original/Amendment), and the count of non-superseded transactions it contains. Use this to look up a specific filing you already have the accession number for (e.g. from GET /v1/filings/recent or GET /v1/transactions); it does not return the individual transaction rows themselves — pull those via GET /v1/transactions filtered by ticker/cik and date. Returns 404 NOT_FOUND if the accession number isn't tracked. Not plan-gated. Query runs live against the database — no caching."""
|
|
1530
1608
|
data = await self._client._get(f"/v1/filings/{accession}")
|
|
1531
1609
|
return FilingResponse._from_dict(data)
|
|
1532
1610
|
|
|
1533
1611
|
async def list(self, *, ticker: str | None = None, cik: str | None = None, from_: str | None = None, to: str | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[FilingResponse]:
|
|
1534
1612
|
"""List Form 4 filings with optional ticker, CIK and date filters
|
|
1535
1613
|
|
|
1536
|
-
Returns a paginated list of Form 4 filings, newest filed first. Filter by ticker, cik, and a from/to filed-date window. Each entry carries the accession number, company ticker/name, period of report, filed date, amendment type (Original/Amendment), and the count of non-superseded transactions in that filing. Use this for a company's filing HISTORY; use GET /v1/filings/recent for a live newest-first feed (it has no page parameter), and GET /v1/transactions when you want the individual trades rather than the filings that contain them. `limit` is accepted as an alias for `per_page`. Not plan-gated."""
|
|
1614
|
+
Returns a paginated list of Form 4 filings, newest filed first. Filter by ticker, cik, and a from/to filed-date window. Each entry carries the accession number, company ticker/name, period of report, filed date, acceptedAt (the precise UTC SEC-acceptance instant, null if not captured), documentUrl (the public SEC document URL), amendment type (Original/Amendment), and the count of non-superseded transactions in that filing. Use this for a company's filing HISTORY; use GET /v1/filings/recent for a live newest-first feed (it has no page parameter), and GET /v1/transactions when you want the individual trades rather than the filings that contain them. `limit` is accepted as an alias for `per_page`. Not plan-gated."""
|
|
1537
1615
|
params = {
|
|
1538
1616
|
"ticker": ticker,
|
|
1539
1617
|
"cik": cik,
|
|
@@ -1550,7 +1628,7 @@ class GeneratedAsyncFilingsResource:
|
|
|
1550
1628
|
async def recent(self, *, ticker: str | None = None, per_page: int | None = None) -> list[FilingResponse]:
|
|
1551
1629
|
"""Get the most recently filed Form 4s, optionally filtered by ticker
|
|
1552
1630
|
|
|
1553
|
-
Returns the most recently filed Form 4s across all companies, newest first, optionally restricted to a single ticker. Use this to monitor new insider activity as it's ingested (e.g. a live "latest filings" feed) rather than for historical or bulk queries — for date-range or filter-heavy queries use GET /v1/transactions with from/to instead. Each entry includes the accession number, company ticker/name, period of report, filed date, amendment type, and the count of non-superseded transactions in that filing. There is no page parameter — this always returns the newest per_page filings, not an arbitrary offset. Not plan-gated."""
|
|
1631
|
+
Returns the most recently filed Form 4s across all companies, newest first, optionally restricted to a single ticker. Use this to monitor new insider activity as it's ingested (e.g. a live "latest filings" feed) rather than for historical or bulk queries — for date-range or filter-heavy queries use GET /v1/transactions with from/to instead. Each entry includes the accession number, company ticker/name, period of report, filed date, acceptedAt (precise UTC SEC-acceptance instant, nullable), documentUrl, amendment type, and the count of non-superseded transactions in that filing. There is no page parameter — this always returns the newest per_page filings, not an arbitrary offset. Not plan-gated."""
|
|
1554
1632
|
params = {
|
|
1555
1633
|
"ticker": ticker,
|
|
1556
1634
|
"per_page": per_page,
|
|
@@ -1564,10 +1642,10 @@ class GeneratedForm144Resource:
|
|
|
1564
1642
|
def __init__(self, client) -> None:
|
|
1565
1643
|
self._client = client
|
|
1566
1644
|
|
|
1567
|
-
def list(self, *, ticker: str | None = None, insider_name: str | None = None, from_: str | None = None, to: str | None = None, exclude_10b5: bool | None = None, page: int | None = None, per_page: int | None = None) -> list[Form144Response]:
|
|
1645
|
+
def list(self, *, ticker: str | None = None, insider_name: str | None = None, from_: str | None = None, to: str | None = None, exclude_10b5: bool | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[Form144Response]:
|
|
1568
1646
|
"""List Form 144 'notice of proposed sale' filings (Business plan+)
|
|
1569
1647
|
|
|
1570
|
-
Returns a paginated list of Form 144 notices — an insider's SEC filing declaring intent to sell restricted/control stock, filed BEFORE the actual sale (which later shows up as a Form 4 TransactionCode=S, typically ~2 days after). Use this as a leading indicator of upcoming insider selling; the isUnder10b5Plan flag on each row separates pre-scheduled 10b5-1 disposals from discretionary intent. Each row includes accession number, ticker/company, insider name/relationship, broker, shares proposed, aggregate market value, approximate sale date, exchange,
|
|
1648
|
+
Returns a paginated list of Form 144 notices — an insider's SEC filing declaring intent to sell restricted/control stock, filed BEFORE the actual sale (which later shows up as a Form 4 TransactionCode=S, typically ~2 days after). Use this as a leading indicator of upcoming insider selling; the isUnder10b5Plan flag on each row separates pre-scheduled 10b5-1 disposals from discretionary intent. Each row includes accession number, ticker/company, insider name/relationship, broker, shares proposed, aggregate market value, approximate sale date, exchange, filed/notice dates, filerCik (from filerCredentials/cik — may be the insider or a filing agent), securitiesClassTitle, and documentUrl (the public SEC document URL). For a bulk historical pull use GET /v1/form144/export instead. Requires Business plan or higher (402 PLAN_REQUIRED on Free/Starter/Pro). `limit` is accepted as an alias for `per_page`. Query runs live against the database — no caching."""
|
|
1571
1649
|
params = {
|
|
1572
1650
|
"ticker": ticker,
|
|
1573
1651
|
"insider_name": insider_name,
|
|
@@ -1576,6 +1654,7 @@ class GeneratedForm144Resource:
|
|
|
1576
1654
|
"exclude_10b5": exclude_10b5,
|
|
1577
1655
|
"page": page,
|
|
1578
1656
|
"per_page": per_page,
|
|
1657
|
+
"limit": limit,
|
|
1579
1658
|
}
|
|
1580
1659
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1581
1660
|
data = self._client._get(f"/v1/form144", params=params)
|
|
@@ -1586,10 +1665,10 @@ class GeneratedAsyncForm144Resource:
|
|
|
1586
1665
|
def __init__(self, client) -> None:
|
|
1587
1666
|
self._client = client
|
|
1588
1667
|
|
|
1589
|
-
async def list(self, *, ticker: str | None = None, insider_name: str | None = None, from_: str | None = None, to: str | None = None, exclude_10b5: bool | None = None, page: int | None = None, per_page: int | None = None) -> list[Form144Response]:
|
|
1668
|
+
async def list(self, *, ticker: str | None = None, insider_name: str | None = None, from_: str | None = None, to: str | None = None, exclude_10b5: bool | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[Form144Response]:
|
|
1590
1669
|
"""List Form 144 'notice of proposed sale' filings (Business plan+)
|
|
1591
1670
|
|
|
1592
|
-
Returns a paginated list of Form 144 notices — an insider's SEC filing declaring intent to sell restricted/control stock, filed BEFORE the actual sale (which later shows up as a Form 4 TransactionCode=S, typically ~2 days after). Use this as a leading indicator of upcoming insider selling; the isUnder10b5Plan flag on each row separates pre-scheduled 10b5-1 disposals from discretionary intent. Each row includes accession number, ticker/company, insider name/relationship, broker, shares proposed, aggregate market value, approximate sale date, exchange,
|
|
1671
|
+
Returns a paginated list of Form 144 notices — an insider's SEC filing declaring intent to sell restricted/control stock, filed BEFORE the actual sale (which later shows up as a Form 4 TransactionCode=S, typically ~2 days after). Use this as a leading indicator of upcoming insider selling; the isUnder10b5Plan flag on each row separates pre-scheduled 10b5-1 disposals from discretionary intent. Each row includes accession number, ticker/company, insider name/relationship, broker, shares proposed, aggregate market value, approximate sale date, exchange, filed/notice dates, filerCik (from filerCredentials/cik — may be the insider or a filing agent), securitiesClassTitle, and documentUrl (the public SEC document URL). For a bulk historical pull use GET /v1/form144/export instead. Requires Business plan or higher (402 PLAN_REQUIRED on Free/Starter/Pro). `limit` is accepted as an alias for `per_page`. Query runs live against the database — no caching."""
|
|
1593
1672
|
params = {
|
|
1594
1673
|
"ticker": ticker,
|
|
1595
1674
|
"insider_name": insider_name,
|
|
@@ -1598,6 +1677,7 @@ class GeneratedAsyncForm144Resource:
|
|
|
1598
1677
|
"exclude_10b5": exclude_10b5,
|
|
1599
1678
|
"page": page,
|
|
1600
1679
|
"per_page": per_page,
|
|
1680
|
+
"limit": limit,
|
|
1601
1681
|
}
|
|
1602
1682
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1603
1683
|
data = await self._client._get(f"/v1/form144", params=params)
|
|
@@ -1608,10 +1688,10 @@ class GeneratedHoldingsResource:
|
|
|
1608
1688
|
def __init__(self, client) -> None:
|
|
1609
1689
|
self._client = client
|
|
1610
1690
|
|
|
1611
|
-
def list(self, *, ticker: str | None = None, cusip: str | None = None, manager_cik: str | None = None, quarter: str | None = None, min_value: float | None = None, page: int | None = None, per_page: int | None = None) -> list[HoldingResponse]:
|
|
1691
|
+
def list(self, *, ticker: str | None = None, cusip: str | None = None, manager_cik: str | None = None, quarter: str | None = None, min_value: float | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[HoldingResponse]:
|
|
1612
1692
|
"""List institutional holdings from Form 13F-HR (Business plan+)
|
|
1613
1693
|
|
|
1614
|
-
Returns a paginated list of individual position rows from Form 13F-HR institutional holdings reports (quarterly disclosures by managers with $100M+ AUM), most recent report period and highest value first. Each row includes the manager name/CIK, report period, issuer name/ticker, CUSIP, security class, position value and share count, share/voting authority type, and the source filing's accession number and filed date. A single security can appear multiple times per manager when sub-managers each report it separately (e.g. Berkshire's subsidiaries). Use GET /v1/managers instead when you want one row per manager (their latest filing + total AUM) rather than position-level detail. Requires Business plan or higher (402 PLAN_REQUIRED on Free/Starter/Pro). Query runs live against the database — no caching; 13F data itself is inherently quarter-lagged (SEC filing deadline is 45 days after quarter end)."""
|
|
1694
|
+
Returns a paginated list of individual position rows from Form 13F-HR institutional holdings reports (quarterly disclosures by managers with $100M+ AUM), most recent report period and highest value first. Each row includes the manager name/CIK, report period, issuer name/ticker, CUSIP, security class, position value and share count, share/voting authority type, and the source filing's accession number and filed date. A single security can appear multiple times per manager when sub-managers each report it separately (e.g. Berkshire's subsidiaries). Use GET /v1/managers instead when you want one row per manager (their latest filing + total AUM) rather than position-level detail. Requires Business plan or higher (402 PLAN_REQUIRED on Free/Starter/Pro). `limit` is accepted as an alias for `per_page`. Query runs live against the database — no caching; 13F data itself is inherently quarter-lagged (SEC filing deadline is 45 days after quarter end)."""
|
|
1615
1695
|
params = {
|
|
1616
1696
|
"ticker": ticker,
|
|
1617
1697
|
"cusip": cusip,
|
|
@@ -1620,20 +1700,22 @@ class GeneratedHoldingsResource:
|
|
|
1620
1700
|
"min_value": min_value,
|
|
1621
1701
|
"page": page,
|
|
1622
1702
|
"per_page": per_page,
|
|
1703
|
+
"limit": limit,
|
|
1623
1704
|
}
|
|
1624
1705
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1625
1706
|
data = self._client._get(f"/v1/holdings", params=params)
|
|
1626
1707
|
return [HoldingResponse._from_dict(item) for item in data]
|
|
1627
1708
|
|
|
1628
|
-
def managers(self, *, name: str | None = None, min_aum: float | None = None, page: int | None = None, per_page: int | None = None) -> list[ManagerResponse]:
|
|
1709
|
+
def managers(self, *, name: str | None = None, min_aum: float | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[ManagerResponse]:
|
|
1629
1710
|
"""List institutional managers with their latest 13F-HR (Business plan+)
|
|
1630
1711
|
|
|
1631
|
-
Returns one row per institutional manager (13F filer), summarising their MOST RECENT 13F-HR filing — manager name/CIK, report period, total reported position value (AUM) and entry count, filed date, and accession number — ranked by AUM descending. Use this for manager-level discovery ("who are the biggest 13F filers?", "rank Apple's institutional holders") before drilling into position detail via GET /v1/holdings?manager_cik=. Amended (IsAmendment=true) filings are excluded from the 'latest' pick. Requires Business plan or higher (402 PLAN_REQUIRED on Free/Starter/Pro). Query runs live against the database — no caching; 13F data is inherently quarter-lagged (SEC deadline is 45 days after quarter end)."""
|
|
1712
|
+
Returns one row per institutional manager (13F filer), summarising their MOST RECENT 13F-HR filing — manager name/CIK, report period, total reported position value (AUM) and entry count, filed date, and accession number — ranked by AUM descending. Use this for manager-level discovery ("who are the biggest 13F filers?", "rank Apple's institutional holders") before drilling into position detail via GET /v1/holdings?manager_cik=. Amended (IsAmendment=true) filings are excluded from the 'latest' pick. Requires Business plan or higher (402 PLAN_REQUIRED on Free/Starter/Pro). `limit` is accepted as an alias for `per_page`. Query runs live against the database — no caching; 13F data is inherently quarter-lagged (SEC deadline is 45 days after quarter end)."""
|
|
1632
1713
|
params = {
|
|
1633
1714
|
"name": name,
|
|
1634
1715
|
"min_aum": min_aum,
|
|
1635
1716
|
"page": page,
|
|
1636
1717
|
"per_page": per_page,
|
|
1718
|
+
"limit": limit,
|
|
1637
1719
|
}
|
|
1638
1720
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1639
1721
|
data = self._client._get(f"/v1/managers", params=params)
|
|
@@ -1644,10 +1726,10 @@ class GeneratedAsyncHoldingsResource:
|
|
|
1644
1726
|
def __init__(self, client) -> None:
|
|
1645
1727
|
self._client = client
|
|
1646
1728
|
|
|
1647
|
-
async def list(self, *, ticker: str | None = None, cusip: str | None = None, manager_cik: str | None = None, quarter: str | None = None, min_value: float | None = None, page: int | None = None, per_page: int | None = None) -> list[HoldingResponse]:
|
|
1729
|
+
async def list(self, *, ticker: str | None = None, cusip: str | None = None, manager_cik: str | None = None, quarter: str | None = None, min_value: float | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[HoldingResponse]:
|
|
1648
1730
|
"""List institutional holdings from Form 13F-HR (Business plan+)
|
|
1649
1731
|
|
|
1650
|
-
Returns a paginated list of individual position rows from Form 13F-HR institutional holdings reports (quarterly disclosures by managers with $100M+ AUM), most recent report period and highest value first. Each row includes the manager name/CIK, report period, issuer name/ticker, CUSIP, security class, position value and share count, share/voting authority type, and the source filing's accession number and filed date. A single security can appear multiple times per manager when sub-managers each report it separately (e.g. Berkshire's subsidiaries). Use GET /v1/managers instead when you want one row per manager (their latest filing + total AUM) rather than position-level detail. Requires Business plan or higher (402 PLAN_REQUIRED on Free/Starter/Pro). Query runs live against the database — no caching; 13F data itself is inherently quarter-lagged (SEC filing deadline is 45 days after quarter end)."""
|
|
1732
|
+
Returns a paginated list of individual position rows from Form 13F-HR institutional holdings reports (quarterly disclosures by managers with $100M+ AUM), most recent report period and highest value first. Each row includes the manager name/CIK, report period, issuer name/ticker, CUSIP, security class, position value and share count, share/voting authority type, and the source filing's accession number and filed date. A single security can appear multiple times per manager when sub-managers each report it separately (e.g. Berkshire's subsidiaries). Use GET /v1/managers instead when you want one row per manager (their latest filing + total AUM) rather than position-level detail. Requires Business plan or higher (402 PLAN_REQUIRED on Free/Starter/Pro). `limit` is accepted as an alias for `per_page`. Query runs live against the database — no caching; 13F data itself is inherently quarter-lagged (SEC filing deadline is 45 days after quarter end)."""
|
|
1651
1733
|
params = {
|
|
1652
1734
|
"ticker": ticker,
|
|
1653
1735
|
"cusip": cusip,
|
|
@@ -1656,20 +1738,22 @@ class GeneratedAsyncHoldingsResource:
|
|
|
1656
1738
|
"min_value": min_value,
|
|
1657
1739
|
"page": page,
|
|
1658
1740
|
"per_page": per_page,
|
|
1741
|
+
"limit": limit,
|
|
1659
1742
|
}
|
|
1660
1743
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1661
1744
|
data = await self._client._get(f"/v1/holdings", params=params)
|
|
1662
1745
|
return [HoldingResponse._from_dict(item) for item in data]
|
|
1663
1746
|
|
|
1664
|
-
async def managers(self, *, name: str | None = None, min_aum: float | None = None, page: int | None = None, per_page: int | None = None) -> list[ManagerResponse]:
|
|
1747
|
+
async def managers(self, *, name: str | None = None, min_aum: float | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[ManagerResponse]:
|
|
1665
1748
|
"""List institutional managers with their latest 13F-HR (Business plan+)
|
|
1666
1749
|
|
|
1667
|
-
Returns one row per institutional manager (13F filer), summarising their MOST RECENT 13F-HR filing — manager name/CIK, report period, total reported position value (AUM) and entry count, filed date, and accession number — ranked by AUM descending. Use this for manager-level discovery ("who are the biggest 13F filers?", "rank Apple's institutional holders") before drilling into position detail via GET /v1/holdings?manager_cik=. Amended (IsAmendment=true) filings are excluded from the 'latest' pick. Requires Business plan or higher (402 PLAN_REQUIRED on Free/Starter/Pro). Query runs live against the database — no caching; 13F data is inherently quarter-lagged (SEC deadline is 45 days after quarter end)."""
|
|
1750
|
+
Returns one row per institutional manager (13F filer), summarising their MOST RECENT 13F-HR filing — manager name/CIK, report period, total reported position value (AUM) and entry count, filed date, and accession number — ranked by AUM descending. Use this for manager-level discovery ("who are the biggest 13F filers?", "rank Apple's institutional holders") before drilling into position detail via GET /v1/holdings?manager_cik=. Amended (IsAmendment=true) filings are excluded from the 'latest' pick. Requires Business plan or higher (402 PLAN_REQUIRED on Free/Starter/Pro). `limit` is accepted as an alias for `per_page`. Query runs live against the database — no caching; 13F data is inherently quarter-lagged (SEC deadline is 45 days after quarter end)."""
|
|
1668
1751
|
params = {
|
|
1669
1752
|
"name": name,
|
|
1670
1753
|
"min_aum": min_aum,
|
|
1671
1754
|
"page": page,
|
|
1672
1755
|
"per_page": per_page,
|
|
1756
|
+
"limit": limit,
|
|
1673
1757
|
}
|
|
1674
1758
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1675
1759
|
data = await self._client._get(f"/v1/managers", params=params)
|
|
@@ -1680,7 +1764,7 @@ class GeneratedInsidersResource:
|
|
|
1680
1764
|
def __init__(self, client) -> None:
|
|
1681
1765
|
self._client = client
|
|
1682
1766
|
|
|
1683
|
-
def directory(self, *, letter: str | None = None, page: int | None = None, per_page: int | None = None) -> InsiderDirectoryResponse:
|
|
1767
|
+
def directory(self, *, letter: str | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> InsiderDirectoryResponse:
|
|
1684
1768
|
"""Browse insiders alphabetically by surname
|
|
1685
1769
|
|
|
1686
1770
|
Returns the A-Z rail with a count per letter, plus one page of insiders under the
|
|
@@ -1692,7 +1776,8 @@ is order by surname. Casing in the source is inconsistent and is not normalised
|
|
|
1692
1776
|
This lists only insiders with at least 3 non-superseded transactions, capped at the
|
|
1693
1777
|
5,000 most active — the same set as the insiders sitemap shard, so the two cannot
|
|
1694
1778
|
drift. To find someone outside that set, use GET /v1/insiders?name= which searches
|
|
1695
|
-
every filer. Rebuilt daily; `refreshedAt` reports when.
|
|
1779
|
+
every filer. Rebuilt daily; `refreshedAt` reports when. `limit` is accepted as an alias for
|
|
1780
|
+
`per_page`. Not plan-gated.
|
|
1696
1781
|
|
|
1697
1782
|
One row per FILER GROUP. A fund group files a single Form 4 listing several
|
|
1698
1783
|
reporting owners — the fund, its GP, its management company — and each is a real
|
|
@@ -1705,6 +1790,7 @@ GET /v1/insiders?name=."""
|
|
|
1705
1790
|
"letter": letter,
|
|
1706
1791
|
"page": page,
|
|
1707
1792
|
"per_page": per_page,
|
|
1793
|
+
"limit": limit,
|
|
1708
1794
|
}
|
|
1709
1795
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1710
1796
|
data = self._client._get(f"/v1/insiders/directory", params=params)
|
|
@@ -1732,14 +1818,15 @@ parameter combination."""
|
|
|
1732
1818
|
data = self._client._get(f"/v1/insiders/leaderboard", params=params)
|
|
1733
1819
|
return InsiderLeaderboardResponse._from_dict(data)
|
|
1734
1820
|
|
|
1735
|
-
def list(self, *, name: str | None = None, page: int | None = None, per_page: int | None = None) -> list[InsiderResponse]:
|
|
1821
|
+
def list(self, *, name: str | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[InsiderResponse]:
|
|
1736
1822
|
"""Search insiders (officers, directors, 10% owners) by name
|
|
1737
1823
|
|
|
1738
|
-
Searches insiders by name and returns a paginated list of matches with each insider's CIK, title, director/officer/10%-owner flags, and total filing count. Use this to resolve a person's name to their CIK before fetching their transaction history, career summary, or scorecard — the CIK returned here feeds directly into GET /v1/insiders/{cik}/transactions, /summary, and /scorecard. Omitting the name filter returns insiders in alphabetical order rather than performing a search. Not plan-gated — available on the Free tier."""
|
|
1824
|
+
Searches insiders by name and returns a paginated list of matches with each insider's CIK, title, director/officer/10%-owner flags, and total filing count. Use this to resolve a person's name to their CIK before fetching their transaction history, career summary, or scorecard — the CIK returned here feeds directly into GET /v1/insiders/{cik}/transactions, /summary, and /scorecard. Omitting the name filter returns insiders in alphabetical order rather than performing a search. `limit` is accepted as an alias for `per_page`. Not plan-gated — available on the Free tier."""
|
|
1739
1825
|
params = {
|
|
1740
1826
|
"name": name,
|
|
1741
1827
|
"page": page,
|
|
1742
1828
|
"per_page": per_page,
|
|
1829
|
+
"limit": limit,
|
|
1743
1830
|
}
|
|
1744
1831
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1745
1832
|
data = self._client._get(f"/v1/insiders", params=params)
|
|
@@ -1783,7 +1870,7 @@ class GeneratedAsyncInsidersResource:
|
|
|
1783
1870
|
def __init__(self, client) -> None:
|
|
1784
1871
|
self._client = client
|
|
1785
1872
|
|
|
1786
|
-
async def directory(self, *, letter: str | None = None, page: int | None = None, per_page: int | None = None) -> InsiderDirectoryResponse:
|
|
1873
|
+
async def directory(self, *, letter: str | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> InsiderDirectoryResponse:
|
|
1787
1874
|
"""Browse insiders alphabetically by surname
|
|
1788
1875
|
|
|
1789
1876
|
Returns the A-Z rail with a count per letter, plus one page of insiders under the
|
|
@@ -1795,7 +1882,8 @@ is order by surname. Casing in the source is inconsistent and is not normalised
|
|
|
1795
1882
|
This lists only insiders with at least 3 non-superseded transactions, capped at the
|
|
1796
1883
|
5,000 most active — the same set as the insiders sitemap shard, so the two cannot
|
|
1797
1884
|
drift. To find someone outside that set, use GET /v1/insiders?name= which searches
|
|
1798
|
-
every filer. Rebuilt daily; `refreshedAt` reports when.
|
|
1885
|
+
every filer. Rebuilt daily; `refreshedAt` reports when. `limit` is accepted as an alias for
|
|
1886
|
+
`per_page`. Not plan-gated.
|
|
1799
1887
|
|
|
1800
1888
|
One row per FILER GROUP. A fund group files a single Form 4 listing several
|
|
1801
1889
|
reporting owners — the fund, its GP, its management company — and each is a real
|
|
@@ -1808,6 +1896,7 @@ GET /v1/insiders?name=."""
|
|
|
1808
1896
|
"letter": letter,
|
|
1809
1897
|
"page": page,
|
|
1810
1898
|
"per_page": per_page,
|
|
1899
|
+
"limit": limit,
|
|
1811
1900
|
}
|
|
1812
1901
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1813
1902
|
data = await self._client._get(f"/v1/insiders/directory", params=params)
|
|
@@ -1835,14 +1924,15 @@ parameter combination."""
|
|
|
1835
1924
|
data = await self._client._get(f"/v1/insiders/leaderboard", params=params)
|
|
1836
1925
|
return InsiderLeaderboardResponse._from_dict(data)
|
|
1837
1926
|
|
|
1838
|
-
async def list(self, *, name: str | None = None, page: int | None = None, per_page: int | None = None) -> list[InsiderResponse]:
|
|
1927
|
+
async def list(self, *, name: str | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[InsiderResponse]:
|
|
1839
1928
|
"""Search insiders (officers, directors, 10% owners) by name
|
|
1840
1929
|
|
|
1841
|
-
Searches insiders by name and returns a paginated list of matches with each insider's CIK, title, director/officer/10%-owner flags, and total filing count. Use this to resolve a person's name to their CIK before fetching their transaction history, career summary, or scorecard — the CIK returned here feeds directly into GET /v1/insiders/{cik}/transactions, /summary, and /scorecard. Omitting the name filter returns insiders in alphabetical order rather than performing a search. Not plan-gated — available on the Free tier."""
|
|
1930
|
+
Searches insiders by name and returns a paginated list of matches with each insider's CIK, title, director/officer/10%-owner flags, and total filing count. Use this to resolve a person's name to their CIK before fetching their transaction history, career summary, or scorecard — the CIK returned here feeds directly into GET /v1/insiders/{cik}/transactions, /summary, and /scorecard. Omitting the name filter returns insiders in alphabetical order rather than performing a search. `limit` is accepted as an alias for `per_page`. Not plan-gated — available on the Free tier."""
|
|
1842
1931
|
params = {
|
|
1843
1932
|
"name": name,
|
|
1844
1933
|
"page": page,
|
|
1845
1934
|
"per_page": per_page,
|
|
1935
|
+
"limit": limit,
|
|
1846
1936
|
}
|
|
1847
1937
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1848
1938
|
data = await self._client._get(f"/v1/insiders", params=params)
|
|
@@ -1886,16 +1976,17 @@ class GeneratedSignalsResource:
|
|
|
1886
1976
|
def __init__(self, client) -> None:
|
|
1887
1977
|
self._client = client
|
|
1888
1978
|
|
|
1889
|
-
def convergence(self, *, ticker: str | None = None, window_days: int | None = None, lookback_days: int | None = None, page: int | None = None, per_page: int | None = None) -> list[ConvergenceEntryDto]:
|
|
1979
|
+
def convergence(self, *, ticker: str | None = None, window_days: int | None = None, lookback_days: int | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[ConvergenceEntryDto]:
|
|
1890
1980
|
"""Insider cluster-buy x congressional-purchase convergence (Pro plan+)
|
|
1891
1981
|
|
|
1892
|
-
Returns the tickers where an insider cluster-buy (InsiderSignal.IsClusterBuy) and at least one non-superseded congressional PURCHASE happened within window_days of EACH OTHER, restricted to convergences where the MORE RECENT of the pair's two dates is within a trailing lookback_days (so this surfaces CURRENT convergences, not ancient history). DEFINITION: for each result, insider.signalDate is the SignalDate of the qualifying cluster-buy signal with the most recent date (insider.insiderCount is that same signal's count — never summed or maxed across multiple signals), and congress is every non-superseded congressional purchase that paired with at least one qualifying cluster-buy (not every purchase in the window — only the ones that actually paired). firstSeen/lastSeen are the earliest/most recent dates among all qualifying insider and congress dates for that ticker. STRENGTH is documented arithmetic, NOT a black-box or predictive/ML score: strength = (distinct congressional purchasers among the qualifying legs) x (the representative signal's insiderCount) — a plain multiplication of two observed counts, nothing more. HONESTY: every congress leg always carries both amountLow and amountHigh (STOCK Act discloses ranges, never exact figures — never combined into a fabricated midpoint) and disclosureLagDays = (disclosureDate - transactionDate); congressional trades are disclosed up to 45 days after the actual trade under the STOCK Act, so this endpoint is detection/monitoring of what insiders AND members of Congress have DISCLOSED buying, not a claim of predictive edge, alpha, or win rate — no performance numbers are computed or implied anywhere in this response. window_days and lookback_days are both caller-overridable with clamps (see each parameter's own description for the exact bounds). Requires Pro plan or higher (402 PLAN_REQUIRED on Free/Starter). Query runs live against the database — no caching."""
|
|
1982
|
+
Returns the tickers where an insider cluster-buy (InsiderSignal.IsClusterBuy) and at least one non-superseded congressional PURCHASE happened within window_days of EACH OTHER, restricted to convergences where the MORE RECENT of the pair's two dates is within a trailing lookback_days (so this surfaces CURRENT convergences, not ancient history). DEFINITION: for each result, insider.signalDate is the SignalDate of the qualifying cluster-buy signal with the most recent date (insider.insiderCount is that same signal's count — never summed or maxed across multiple signals), and congress is every non-superseded congressional purchase that paired with at least one qualifying cluster-buy (not every purchase in the window — only the ones that actually paired). firstSeen/lastSeen are the earliest/most recent dates among all qualifying insider and congress dates for that ticker. STRENGTH is documented arithmetic, NOT a black-box or predictive/ML score: strength = (distinct congressional purchasers among the qualifying legs) x (the representative signal's insiderCount) — a plain multiplication of two observed counts, nothing more. HONESTY: every congress leg always carries both amountLow and amountHigh (STOCK Act discloses ranges, never exact figures — never combined into a fabricated midpoint) and disclosureLagDays = (disclosureDate - transactionDate); congressional trades are disclosed up to 45 days after the actual trade under the STOCK Act, so this endpoint is detection/monitoring of what insiders AND members of Congress have DISCLOSED buying, not a claim of predictive edge, alpha, or win rate — no performance numbers are computed or implied anywhere in this response. window_days and lookback_days are both caller-overridable with clamps (see each parameter's own description for the exact bounds). Requires Pro plan or higher (402 PLAN_REQUIRED on Free/Starter). `limit` is accepted as an alias for `per_page`. Query runs live against the database — no caching."""
|
|
1893
1983
|
params = {
|
|
1894
1984
|
"ticker": ticker,
|
|
1895
1985
|
"window_days": window_days,
|
|
1896
1986
|
"lookback_days": lookback_days,
|
|
1897
1987
|
"page": page,
|
|
1898
1988
|
"per_page": per_page,
|
|
1989
|
+
"limit": limit,
|
|
1899
1990
|
}
|
|
1900
1991
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1901
1992
|
data = self._client._get(f"/v1/signals/convergence", params=params)
|
|
@@ -1928,16 +2019,17 @@ class GeneratedAsyncSignalsResource:
|
|
|
1928
2019
|
def __init__(self, client) -> None:
|
|
1929
2020
|
self._client = client
|
|
1930
2021
|
|
|
1931
|
-
async def convergence(self, *, ticker: str | None = None, window_days: int | None = None, lookback_days: int | None = None, page: int | None = None, per_page: int | None = None) -> list[ConvergenceEntryDto]:
|
|
2022
|
+
async def convergence(self, *, ticker: str | None = None, window_days: int | None = None, lookback_days: int | None = None, page: int | None = None, per_page: int | None = None, limit: int | None = None) -> list[ConvergenceEntryDto]:
|
|
1932
2023
|
"""Insider cluster-buy x congressional-purchase convergence (Pro plan+)
|
|
1933
2024
|
|
|
1934
|
-
Returns the tickers where an insider cluster-buy (InsiderSignal.IsClusterBuy) and at least one non-superseded congressional PURCHASE happened within window_days of EACH OTHER, restricted to convergences where the MORE RECENT of the pair's two dates is within a trailing lookback_days (so this surfaces CURRENT convergences, not ancient history). DEFINITION: for each result, insider.signalDate is the SignalDate of the qualifying cluster-buy signal with the most recent date (insider.insiderCount is that same signal's count — never summed or maxed across multiple signals), and congress is every non-superseded congressional purchase that paired with at least one qualifying cluster-buy (not every purchase in the window — only the ones that actually paired). firstSeen/lastSeen are the earliest/most recent dates among all qualifying insider and congress dates for that ticker. STRENGTH is documented arithmetic, NOT a black-box or predictive/ML score: strength = (distinct congressional purchasers among the qualifying legs) x (the representative signal's insiderCount) — a plain multiplication of two observed counts, nothing more. HONESTY: every congress leg always carries both amountLow and amountHigh (STOCK Act discloses ranges, never exact figures — never combined into a fabricated midpoint) and disclosureLagDays = (disclosureDate - transactionDate); congressional trades are disclosed up to 45 days after the actual trade under the STOCK Act, so this endpoint is detection/monitoring of what insiders AND members of Congress have DISCLOSED buying, not a claim of predictive edge, alpha, or win rate — no performance numbers are computed or implied anywhere in this response. window_days and lookback_days are both caller-overridable with clamps (see each parameter's own description for the exact bounds). Requires Pro plan or higher (402 PLAN_REQUIRED on Free/Starter). Query runs live against the database — no caching."""
|
|
2025
|
+
Returns the tickers where an insider cluster-buy (InsiderSignal.IsClusterBuy) and at least one non-superseded congressional PURCHASE happened within window_days of EACH OTHER, restricted to convergences where the MORE RECENT of the pair's two dates is within a trailing lookback_days (so this surfaces CURRENT convergences, not ancient history). DEFINITION: for each result, insider.signalDate is the SignalDate of the qualifying cluster-buy signal with the most recent date (insider.insiderCount is that same signal's count — never summed or maxed across multiple signals), and congress is every non-superseded congressional purchase that paired with at least one qualifying cluster-buy (not every purchase in the window — only the ones that actually paired). firstSeen/lastSeen are the earliest/most recent dates among all qualifying insider and congress dates for that ticker. STRENGTH is documented arithmetic, NOT a black-box or predictive/ML score: strength = (distinct congressional purchasers among the qualifying legs) x (the representative signal's insiderCount) — a plain multiplication of two observed counts, nothing more. HONESTY: every congress leg always carries both amountLow and amountHigh (STOCK Act discloses ranges, never exact figures — never combined into a fabricated midpoint) and disclosureLagDays = (disclosureDate - transactionDate); congressional trades are disclosed up to 45 days after the actual trade under the STOCK Act, so this endpoint is detection/monitoring of what insiders AND members of Congress have DISCLOSED buying, not a claim of predictive edge, alpha, or win rate — no performance numbers are computed or implied anywhere in this response. window_days and lookback_days are both caller-overridable with clamps (see each parameter's own description for the exact bounds). Requires Pro plan or higher (402 PLAN_REQUIRED on Free/Starter). `limit` is accepted as an alias for `per_page`. Query runs live against the database — no caching."""
|
|
1935
2026
|
params = {
|
|
1936
2027
|
"ticker": ticker,
|
|
1937
2028
|
"window_days": window_days,
|
|
1938
2029
|
"lookback_days": lookback_days,
|
|
1939
2030
|
"page": page,
|
|
1940
2031
|
"per_page": per_page,
|
|
2032
|
+
"limit": limit,
|
|
1941
2033
|
}
|
|
1942
2034
|
params = {k: str(v) for k, v in params.items() if v is not None}
|
|
1943
2035
|
data = await self._client._get(f"/v1/signals/convergence", params=params)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, fields
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class TopHolder:
|
|
8
|
+
manager_cik: str | None = None
|
|
9
|
+
manager_name: str | None = None
|
|
10
|
+
shares: float | None = None
|
|
11
|
+
value: float | None = None
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def _from_dict(cls, data: dict) -> "TopHolder":
|
|
15
|
+
known = {f.name for f in fields(cls)}
|
|
16
|
+
return cls(**{k: v for k, v in data.items() if k in known})
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class InstitutionalOwnership:
|
|
21
|
+
quarter: str | None = None
|
|
22
|
+
total_aum_usd: float | None = None
|
|
23
|
+
delta_qoq_pct: float | None = None
|
|
24
|
+
trend: str | None = None
|
|
25
|
+
top_holders: list[TopHolder] | None = None
|
|
26
|
+
coverage_incomplete: bool | None = None
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def _from_dict(cls, data: dict) -> "InstitutionalOwnership":
|
|
30
|
+
known = {f.name for f in fields(cls)}
|
|
31
|
+
kwargs = {k: v for k, v in data.items() if k in known}
|
|
32
|
+
if isinstance(kwargs.get("top_holders"), list):
|
|
33
|
+
kwargs["top_holders"] = [
|
|
34
|
+
TopHolder._from_dict(i) if isinstance(i, dict) else i for i in kwargs["top_holders"]
|
|
35
|
+
]
|
|
36
|
+
return cls(**kwargs)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class Transaction:
|
|
41
|
+
ticker: str
|
|
42
|
+
company_name: str
|
|
43
|
+
insider_name: str
|
|
44
|
+
insider_cik: str
|
|
45
|
+
insider_title: str | None
|
|
46
|
+
is_director: bool
|
|
47
|
+
is_officer: bool
|
|
48
|
+
is10_pct_owner: bool
|
|
49
|
+
accession_number: str
|
|
50
|
+
security_title: str
|
|
51
|
+
transaction_code: str
|
|
52
|
+
is_open_market: bool
|
|
53
|
+
is10b5_plan: bool
|
|
54
|
+
shares_amount: float
|
|
55
|
+
price_per_share: float | None
|
|
56
|
+
total_value: float | None
|
|
57
|
+
shares_owned_after: float | None
|
|
58
|
+
direct_indirect: str | None
|
|
59
|
+
is_derivative: bool
|
|
60
|
+
transaction_date: str
|
|
61
|
+
period_of_report: str
|
|
62
|
+
# Added 2026-09-24: return columns, source-quality flag, SEC acceptance
|
|
63
|
+
# timestamp, and the public document URL. All optional so an older SDK
|
|
64
|
+
# keeps working against a payload that doesn't send them, and a newer
|
|
65
|
+
# backend can add more without breaking this dataclass's constructor.
|
|
66
|
+
return1d: float | None = None
|
|
67
|
+
return1w: float | None = None
|
|
68
|
+
return1m: float | None = None
|
|
69
|
+
return3m: float | None = None
|
|
70
|
+
return6m: float | None = None
|
|
71
|
+
value_quality: str | None = None
|
|
72
|
+
accepted_at: str | None = None
|
|
73
|
+
document_url: str | None = None
|
|
74
|
+
institutional_ownership: InstitutionalOwnership | None = None
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def _from_dict(cls, data: dict) -> "Transaction":
|
|
78
|
+
"""Build from an API payload, ignoring unknown keys and hydrating the
|
|
79
|
+
nested `institutional_ownership` object instead of leaving it a raw
|
|
80
|
+
dict (see codegen/generate.py for the history of that bug)."""
|
|
81
|
+
known = {f.name for f in fields(cls)}
|
|
82
|
+
kwargs = {k: v for k, v in data.items() if k in known}
|
|
83
|
+
if isinstance(kwargs.get("institutional_ownership"), dict):
|
|
84
|
+
kwargs["institutional_ownership"] = InstitutionalOwnership._from_dict(
|
|
85
|
+
kwargs["institutional_ownership"]
|
|
86
|
+
)
|
|
87
|
+
return cls(**kwargs)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class Insider:
|
|
92
|
+
cik: str
|
|
93
|
+
name: str
|
|
94
|
+
is_director: bool
|
|
95
|
+
is_officer: bool
|
|
96
|
+
is_ten_percent_owner: bool
|
|
97
|
+
officer_title: str | None
|
|
98
|
+
total_filings: int
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass
|
|
102
|
+
class Company:
|
|
103
|
+
cik: str
|
|
104
|
+
name: str
|
|
105
|
+
ticker: str | None
|
|
106
|
+
exchange: str | None
|
|
107
|
+
total_filings: int
|
|
108
|
+
active_insiders: int
|
|
109
|
+
sic_description: str | None
|
|
110
|
+
state_of_incorporation: str | None
|
|
111
|
+
website: str | None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass
|
|
115
|
+
class InsiderSignal:
|
|
116
|
+
ticker: str | None
|
|
117
|
+
company_name: str
|
|
118
|
+
signal_date: str
|
|
119
|
+
buy_sell_ratio: float
|
|
120
|
+
is_cluster_buy: bool
|
|
121
|
+
is_cluster_sell: bool
|
|
122
|
+
insider_count: int
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass
|
|
126
|
+
class WebhookCreated:
|
|
127
|
+
subscription_id: int
|
|
128
|
+
url: str
|
|
129
|
+
event_types: list[str]
|
|
130
|
+
secret: str
|
|
131
|
+
created_at: str
|
|
132
|
+
warning: str | None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass
|
|
136
|
+
class WebhookSubscription:
|
|
137
|
+
subscription_id: int
|
|
138
|
+
url: str
|
|
139
|
+
event_types: list[str]
|
|
140
|
+
created_at: str
|
|
141
|
+
is_active: bool
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass
|
|
145
|
+
class WebhookEvent:
|
|
146
|
+
delivery_id: int
|
|
147
|
+
subscription_id: int
|
|
148
|
+
event_type: str
|
|
149
|
+
attempt_count: int
|
|
150
|
+
delivered_at: str | None
|
|
151
|
+
next_retry_at: str | None
|
|
152
|
+
last_status_code: int | None
|
|
153
|
+
is_dead: bool
|
|
154
|
+
payload: str
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
@@ -56,7 +56,7 @@ class InsidersResource(GeneratedInsidersResource):
|
|
|
56
56
|
) -> list[Transaction]:
|
|
57
57
|
params = _transactions_params(from_date, to_date, page, per_page)
|
|
58
58
|
data = self._client._get(f"/v1/insiders/{cik}/transactions", params)
|
|
59
|
-
return [Transaction(
|
|
59
|
+
return [Transaction._from_dict(item) for item in data]
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
class AsyncInsidersResource(GeneratedAsyncInsidersResource):
|
|
@@ -84,7 +84,7 @@ class AsyncInsidersResource(GeneratedAsyncInsidersResource):
|
|
|
84
84
|
) -> list[Transaction]:
|
|
85
85
|
params = _transactions_params(from_date, to_date, page, per_page)
|
|
86
86
|
data = await self._client._get(f"/v1/insiders/{cik}/transactions", params)
|
|
87
|
-
return [Transaction(
|
|
87
|
+
return [Transaction._from_dict(item) for item in data]
|
|
88
88
|
|
|
89
89
|
|
|
90
90
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import AsyncGenerator, Generator
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
@@ -18,6 +18,8 @@ def _list_params(
|
|
|
18
18
|
code: str | None = None,
|
|
19
19
|
from_date: str | None = None,
|
|
20
20
|
to_date: str | None = None,
|
|
21
|
+
filed_from: str | None = None,
|
|
22
|
+
filed_to: str | None = None,
|
|
21
23
|
exclude_10b5: bool | None = None,
|
|
22
24
|
codes: str | None = None,
|
|
23
25
|
exclude_codes: str | None = None,
|
|
@@ -34,14 +36,16 @@ def _list_params(
|
|
|
34
36
|
) -> dict[str, str]:
|
|
35
37
|
"""Shared by the sync and async twins.
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
Twenty optional filters is exactly the kind of block that drifts when it
|
|
38
40
|
is copy-pasted, so it lives in one place and both twins call it. Only the
|
|
39
41
|
signature is duplicated, and only because that is what preserves type hints.
|
|
40
42
|
"""
|
|
41
43
|
params: dict[str, str] = {"page": str(page), "per_page": str(per_page)}
|
|
42
44
|
for key, value in (
|
|
43
45
|
("ticker", ticker), ("cik", cik), ("insider_cik", insider_cik), ("code", code),
|
|
44
|
-
("from", from_date), ("to", to_date),
|
|
46
|
+
("from", from_date), ("to", to_date),
|
|
47
|
+
("filed_from", filed_from), ("filed_to", filed_to),
|
|
48
|
+
("codes", codes),
|
|
45
49
|
("exclude_codes", exclude_codes), ("category", category),
|
|
46
50
|
("exclude_category", exclude_category),
|
|
47
51
|
):
|
|
@@ -75,6 +79,8 @@ class TransactionsResource:
|
|
|
75
79
|
code: str | None = None,
|
|
76
80
|
from_date: str | None = None,
|
|
77
81
|
to_date: str | None = None,
|
|
82
|
+
filed_from: str | None = None,
|
|
83
|
+
filed_to: str | None = None,
|
|
78
84
|
exclude_10b5: bool | None = None,
|
|
79
85
|
codes: str | None = None,
|
|
80
86
|
exclude_codes: str | None = None,
|
|
@@ -89,9 +95,20 @@ class TransactionsResource:
|
|
|
89
95
|
page: int = 1,
|
|
90
96
|
per_page: int = 50,
|
|
91
97
|
) -> list[Transaction]:
|
|
98
|
+
"""List transactions.
|
|
99
|
+
|
|
100
|
+
`ticker` accepts up to 25 comma-separated symbols (e.g.
|
|
101
|
+
`ticker="AAPL,MSFT,NVDA"`), same convention as `codes`/`exclude_codes`.
|
|
102
|
+
`filed_from`/`filed_to` filter on Filing.FiledAt (inclusive) — the date
|
|
103
|
+
the filing hit EDGAR — as opposed to `from_date`/`to_date`, which filter
|
|
104
|
+
on the transaction's own reported date. Accepts a date or ISO string,
|
|
105
|
+
same as `from_date`/`to_date`.
|
|
106
|
+
"""
|
|
92
107
|
params = _list_params(
|
|
93
108
|
ticker=ticker, cik=cik, insider_cik=insider_cik, code=code,
|
|
94
|
-
from_date=from_date, to_date=to_date,
|
|
109
|
+
from_date=from_date, to_date=to_date,
|
|
110
|
+
filed_from=filed_from, filed_to=filed_to,
|
|
111
|
+
exclude_10b5=exclude_10b5,
|
|
95
112
|
codes=codes, exclude_codes=exclude_codes, category=category,
|
|
96
113
|
exclude_category=exclude_category, exclude_derivative=exclude_derivative,
|
|
97
114
|
significant=significant, min_value=min_value, max_value=max_value,
|
|
@@ -99,7 +116,7 @@ class TransactionsResource:
|
|
|
99
116
|
page=page, per_page=per_page,
|
|
100
117
|
)
|
|
101
118
|
data = self._client._get("/v1/transactions", params)
|
|
102
|
-
return [Transaction(
|
|
119
|
+
return [Transaction._from_dict(item) for item in data]
|
|
103
120
|
|
|
104
121
|
def paginate(
|
|
105
122
|
self,
|
|
@@ -110,6 +127,8 @@ class TransactionsResource:
|
|
|
110
127
|
code: str | None = None,
|
|
111
128
|
from_date: str | None = None,
|
|
112
129
|
to_date: str | None = None,
|
|
130
|
+
filed_from: str | None = None,
|
|
131
|
+
filed_to: str | None = None,
|
|
113
132
|
exclude_10b5: bool | None = None,
|
|
114
133
|
codes: str | None = None,
|
|
115
134
|
exclude_codes: str | None = None,
|
|
@@ -144,6 +163,7 @@ class TransactionsResource:
|
|
|
144
163
|
batch = self.list(
|
|
145
164
|
ticker=ticker, cik=cik, insider_cik=insider_cik,
|
|
146
165
|
code=code, from_date=from_date, to_date=to_date,
|
|
166
|
+
filed_from=filed_from, filed_to=filed_to,
|
|
147
167
|
exclude_10b5=exclude_10b5,
|
|
148
168
|
codes=codes, exclude_codes=exclude_codes,
|
|
149
169
|
category=category, exclude_category=exclude_category,
|
|
@@ -185,6 +205,8 @@ class AsyncTransactionsResource:
|
|
|
185
205
|
code: str | None = None,
|
|
186
206
|
from_date: str | None = None,
|
|
187
207
|
to_date: str | None = None,
|
|
208
|
+
filed_from: str | None = None,
|
|
209
|
+
filed_to: str | None = None,
|
|
188
210
|
exclude_10b5: bool | None = None,
|
|
189
211
|
codes: str | None = None,
|
|
190
212
|
exclude_codes: str | None = None,
|
|
@@ -199,9 +221,12 @@ class AsyncTransactionsResource:
|
|
|
199
221
|
page: int = 1,
|
|
200
222
|
per_page: int = 50,
|
|
201
223
|
) -> list[Transaction]:
|
|
224
|
+
"""See `TransactionsResource.list` for `ticker`/`filed_from`/`filed_to` semantics."""
|
|
202
225
|
params = _list_params(
|
|
203
226
|
ticker=ticker, cik=cik, insider_cik=insider_cik, code=code,
|
|
204
|
-
from_date=from_date, to_date=to_date,
|
|
227
|
+
from_date=from_date, to_date=to_date,
|
|
228
|
+
filed_from=filed_from, filed_to=filed_to,
|
|
229
|
+
exclude_10b5=exclude_10b5,
|
|
205
230
|
codes=codes, exclude_codes=exclude_codes, category=category,
|
|
206
231
|
exclude_category=exclude_category, exclude_derivative=exclude_derivative,
|
|
207
232
|
significant=significant, min_value=min_value, max_value=max_value,
|
|
@@ -209,7 +234,7 @@ class AsyncTransactionsResource:
|
|
|
209
234
|
page=page, per_page=per_page,
|
|
210
235
|
)
|
|
211
236
|
data = await self._client._get("/v1/transactions", params)
|
|
212
|
-
return [Transaction(
|
|
237
|
+
return [Transaction._from_dict(item) for item in data]
|
|
213
238
|
|
|
214
239
|
async def paginate(
|
|
215
240
|
self,
|
|
@@ -220,6 +245,8 @@ class AsyncTransactionsResource:
|
|
|
220
245
|
code: str | None = None,
|
|
221
246
|
from_date: str | None = None,
|
|
222
247
|
to_date: str | None = None,
|
|
248
|
+
filed_from: str | None = None,
|
|
249
|
+
filed_to: str | None = None,
|
|
223
250
|
exclude_10b5: bool | None = None,
|
|
224
251
|
codes: str | None = None,
|
|
225
252
|
exclude_codes: str | None = None,
|
|
@@ -245,7 +272,9 @@ class AsyncTransactionsResource:
|
|
|
245
272
|
try:
|
|
246
273
|
batch = await self.list(
|
|
247
274
|
ticker=ticker, cik=cik, insider_cik=insider_cik, code=code,
|
|
248
|
-
from_date=from_date, to_date=to_date,
|
|
275
|
+
from_date=from_date, to_date=to_date,
|
|
276
|
+
filed_from=filed_from, filed_to=filed_to,
|
|
277
|
+
exclude_10b5=exclude_10b5,
|
|
249
278
|
codes=codes, exclude_codes=exclude_codes, category=category,
|
|
250
279
|
exclude_category=exclude_category, exclude_derivative=exclude_derivative,
|
|
251
280
|
significant=significant, min_value=min_value, max_value=max_value,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: form4api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Python client for the Form4API — real-time SEC Form 4 insider trading, Form 144 intent-to-sell, institutional 13F-HR, and congressional STOCK Act trading data, including the insider/Congress convergence signal
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://www.form4api.com
|
|
@@ -108,16 +108,21 @@ For LLM workflows, `form4api-mcp` exposes the same endpoints as tools.
|
|
|
108
108
|
|
|
109
109
|
```python
|
|
110
110
|
client.transactions.list(
|
|
111
|
-
ticker="AAPL", # filter by ticker
|
|
111
|
+
ticker="AAPL", # filter by ticker — accepts up to 25 comma-separated symbols
|
|
112
112
|
cik="0000320193", # or by company CIK
|
|
113
113
|
insider_cik="...", # filter by insider CIK
|
|
114
114
|
code="P", # transaction code: P=purchase, S=sale, A=grant, etc.
|
|
115
115
|
from_date="2026-01-01",
|
|
116
116
|
to_date="2026-12-31",
|
|
117
|
+
filed_from="2026-01-01", # filters on Filing.FiledAt (when it hit EDGAR), not the trade date
|
|
118
|
+
filed_to="2026-12-31",
|
|
117
119
|
exclude_10b5=True, # omit trades filed under a Rule 10b5-1 plan
|
|
118
120
|
per_page=100,
|
|
119
121
|
page=1,
|
|
120
122
|
)
|
|
123
|
+
|
|
124
|
+
# Multi-ticker lookup in one call
|
|
125
|
+
client.transactions.list(ticker="AAPL,MSFT,NVDA")
|
|
121
126
|
```
|
|
122
127
|
|
|
123
128
|
### Granular filtering (v0.4.0+)
|
|
@@ -166,6 +171,11 @@ client.transactions.list(min_shares=10_000, max_shares=100_000)
|
|
|
166
171
|
| `is_derivative` | `bool` | Derivative security flag |
|
|
167
172
|
| `transaction_date` | `str` | ISO datetime |
|
|
168
173
|
| `period_of_report` | `str` | ISO datetime |
|
|
174
|
+
| `return1d`/`return1w`/`return1m`/`return3m`/`return6m` | `float \| None` | Forward return since the transaction date, as a fraction (0.109 = +10.9%); `None` until enough time/price data has elapsed |
|
|
175
|
+
| `value_quality` | `str \| None` | Confidence flag on `total_value`'s pricing source |
|
|
176
|
+
| `accepted_at` | `str \| None` | Precise UTC SEC-acceptance instant; `None` if not captured |
|
|
177
|
+
| `document_url` | `str \| None` | Public SEC document URL |
|
|
178
|
+
| `institutional_ownership` | `InstitutionalOwnership \| None` | Latest 13F institutional ownership snapshot for the ticker, when available |
|
|
169
179
|
|
|
170
180
|
### Company fields
|
|
171
181
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "form4api"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.8.0"
|
|
8
8
|
description = "Python client for the Form4API — real-time SEC Form 4 insider trading, Form 144 intent-to-sell, institutional 13F-HR, and congressional STOCK Act trading data, including the insider/Congress convergence signal"
|
|
9
9
|
keywords = ["insider-trading", "sec", "sec-edgar", "edgar", "form-4", "form4", "form-144", "13f", "13f-hr", "congress", "stock-act", "financial-data", "stock-market", "stocks", "fintech", "api", "sdk", "webhooks", "form4api"]
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -151,6 +151,66 @@ def test_transactions_list_sends_granular_filters(client):
|
|
|
151
151
|
assert qs["max_shares"] == "10000"
|
|
152
152
|
|
|
153
153
|
|
|
154
|
+
@respx.mock
|
|
155
|
+
def test_transactions_list_sends_filed_from_and_filed_to(client):
|
|
156
|
+
"""filed_from/filed_to filter on Filing.FiledAt, separate from the
|
|
157
|
+
transaction-date from_date/to_date pair."""
|
|
158
|
+
route = respx.get(f"{BASE}/v1/transactions").mock(return_value=httpx.Response(200, json=[]))
|
|
159
|
+
client.transactions.list(
|
|
160
|
+
ticker="AAPL,MSFT,NVDA",
|
|
161
|
+
filed_from="2026-01-01",
|
|
162
|
+
filed_to="2026-01-31",
|
|
163
|
+
)
|
|
164
|
+
assert route.called
|
|
165
|
+
qs = dict(route.calls[0].request.url.params)
|
|
166
|
+
assert qs["ticker"] == "AAPL,MSFT,NVDA"
|
|
167
|
+
assert qs["filed_from"] == "2026-01-01"
|
|
168
|
+
assert qs["filed_to"] == "2026-01-31"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@respx.mock
|
|
172
|
+
def test_transactions_list_hydrates_new_fields(client):
|
|
173
|
+
"""acceptedAt/documentUrl and the nested institutionalOwnership object
|
|
174
|
+
hydrate onto the Transaction dataclass instead of raising or staying a
|
|
175
|
+
raw dict."""
|
|
176
|
+
tx = {
|
|
177
|
+
**TX,
|
|
178
|
+
"return1d": 0.012,
|
|
179
|
+
"return1w": None,
|
|
180
|
+
"valueQuality": "high",
|
|
181
|
+
"acceptedAt": "2026-01-15T21:03:00Z",
|
|
182
|
+
"documentUrl": "https://www.sec.gov/Archives/edgar/data/1/doc.xml",
|
|
183
|
+
"institutionalOwnership": {
|
|
184
|
+
"quarter": "2025-12-31T00:00:00Z",
|
|
185
|
+
"totalAumUsd": 1_000_000.0,
|
|
186
|
+
"deltaQoqPct": 0.05,
|
|
187
|
+
"trend": "increasing",
|
|
188
|
+
"topHolders": [
|
|
189
|
+
{"managerCik": "0001", "managerName": "Big Fund", "shares": 100.0, "value": 200.0}
|
|
190
|
+
],
|
|
191
|
+
"coverageIncomplete": False,
|
|
192
|
+
},
|
|
193
|
+
}
|
|
194
|
+
respx.get(f"{BASE}/v1/transactions").mock(return_value=httpx.Response(200, json=[tx]))
|
|
195
|
+
result = client.transactions.list()[0]
|
|
196
|
+
assert result.accepted_at == "2026-01-15T21:03:00Z"
|
|
197
|
+
assert result.document_url == "https://www.sec.gov/Archives/edgar/data/1/doc.xml"
|
|
198
|
+
assert result.value_quality == "high"
|
|
199
|
+
assert result.return1d == 0.012
|
|
200
|
+
assert result.institutional_ownership.total_aum_usd == 1_000_000.0
|
|
201
|
+
assert result.institutional_ownership.top_holders[0].manager_name == "Big Fund"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@respx.mock
|
|
205
|
+
def test_transactions_list_tolerates_missing_new_fields(client):
|
|
206
|
+
"""A payload from before the new fields shipped (no acceptedAt/documentUrl/
|
|
207
|
+
institutionalOwnership) still parses — everything new is optional."""
|
|
208
|
+
respx.get(f"{BASE}/v1/transactions").mock(return_value=httpx.Response(200, json=[TX]))
|
|
209
|
+
result = client.transactions.list()[0]
|
|
210
|
+
assert result.accepted_at is None
|
|
211
|
+
assert result.institutional_ownership is None
|
|
212
|
+
|
|
213
|
+
|
|
154
214
|
@respx.mock
|
|
155
215
|
def test_transactions_paginate_stops_on_short_page(client):
|
|
156
216
|
respx.get(f"{BASE}/v1/transactions").mock(side_effect=[
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@dataclass
|
|
7
|
-
class Transaction:
|
|
8
|
-
ticker: str
|
|
9
|
-
company_name: str
|
|
10
|
-
insider_name: str
|
|
11
|
-
insider_cik: str
|
|
12
|
-
insider_title: str | None
|
|
13
|
-
is_director: bool
|
|
14
|
-
is_officer: bool
|
|
15
|
-
is10_pct_owner: bool
|
|
16
|
-
accession_number: str
|
|
17
|
-
security_title: str
|
|
18
|
-
transaction_code: str
|
|
19
|
-
is_open_market: bool
|
|
20
|
-
is10b5_plan: bool
|
|
21
|
-
shares_amount: float
|
|
22
|
-
price_per_share: float | None
|
|
23
|
-
total_value: float | None
|
|
24
|
-
shares_owned_after: float | None
|
|
25
|
-
direct_indirect: str | None
|
|
26
|
-
is_derivative: bool
|
|
27
|
-
transaction_date: str
|
|
28
|
-
period_of_report: str
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@dataclass
|
|
32
|
-
class Insider:
|
|
33
|
-
cik: str
|
|
34
|
-
name: str
|
|
35
|
-
is_director: bool
|
|
36
|
-
is_officer: bool
|
|
37
|
-
is_ten_percent_owner: bool
|
|
38
|
-
officer_title: str | None
|
|
39
|
-
total_filings: int
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@dataclass
|
|
43
|
-
class Company:
|
|
44
|
-
cik: str
|
|
45
|
-
name: str
|
|
46
|
-
ticker: str | None
|
|
47
|
-
exchange: str | None
|
|
48
|
-
total_filings: int
|
|
49
|
-
active_insiders: int
|
|
50
|
-
sic_description: str | None
|
|
51
|
-
state_of_incorporation: str | None
|
|
52
|
-
website: str | None
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@dataclass
|
|
56
|
-
class InsiderSignal:
|
|
57
|
-
ticker: str | None
|
|
58
|
-
company_name: str
|
|
59
|
-
signal_date: str
|
|
60
|
-
buy_sell_ratio: float
|
|
61
|
-
is_cluster_buy: bool
|
|
62
|
-
is_cluster_sell: bool
|
|
63
|
-
insider_count: int
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@dataclass
|
|
67
|
-
class WebhookCreated:
|
|
68
|
-
subscription_id: int
|
|
69
|
-
url: str
|
|
70
|
-
event_types: list[str]
|
|
71
|
-
secret: str
|
|
72
|
-
created_at: str
|
|
73
|
-
warning: str | None
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
@dataclass
|
|
77
|
-
class WebhookSubscription:
|
|
78
|
-
subscription_id: int
|
|
79
|
-
url: str
|
|
80
|
-
event_types: list[str]
|
|
81
|
-
created_at: str
|
|
82
|
-
is_active: bool
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
@dataclass
|
|
86
|
-
class WebhookEvent:
|
|
87
|
-
delivery_id: int
|
|
88
|
-
subscription_id: int
|
|
89
|
-
event_type: str
|
|
90
|
-
attempt_count: int
|
|
91
|
-
delivered_at: str | None
|
|
92
|
-
next_retry_at: str | None
|
|
93
|
-
last_status_code: int | None
|
|
94
|
-
is_dead: bool
|
|
95
|
-
payload: str
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|