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.
Files changed (26) hide show
  1. {form4api-0.7.1 → form4api-0.8.0}/PKG-INFO +12 -2
  2. {form4api-0.7.1 → form4api-0.8.0}/README.md +11 -1
  3. {form4api-0.7.1 → form4api-0.8.0}/form4api/__init__.py +4 -0
  4. {form4api-0.7.1 → form4api-0.8.0}/form4api/_generated.py +130 -38
  5. form4api-0.8.0/form4api/_types.py +157 -0
  6. {form4api-0.7.1 → form4api-0.8.0}/form4api/resources/_insiders.py +2 -2
  7. {form4api-0.7.1 → form4api-0.8.0}/form4api/resources/_transactions.py +37 -8
  8. {form4api-0.7.1 → form4api-0.8.0}/form4api.egg-info/PKG-INFO +12 -2
  9. {form4api-0.7.1 → form4api-0.8.0}/pyproject.toml +1 -1
  10. {form4api-0.7.1 → form4api-0.8.0}/tests/test_client.py +60 -0
  11. form4api-0.7.1/form4api/_types.py +0 -98
  12. {form4api-0.7.1 → form4api-0.8.0}/LICENSE +0 -0
  13. {form4api-0.7.1 → form4api-0.8.0}/form4api/_client.py +0 -0
  14. {form4api-0.7.1 → form4api-0.8.0}/form4api/_errors.py +0 -0
  15. {form4api-0.7.1 → form4api-0.8.0}/form4api/_webhook_utils.py +0 -0
  16. {form4api-0.7.1 → form4api-0.8.0}/form4api/resources/__init__.py +0 -0
  17. {form4api-0.7.1 → form4api-0.8.0}/form4api/resources/_companies.py +0 -0
  18. {form4api-0.7.1 → form4api-0.8.0}/form4api/resources/_signals.py +0 -0
  19. {form4api-0.7.1 → form4api-0.8.0}/form4api/resources/_webhooks.py +0 -0
  20. {form4api-0.7.1 → form4api-0.8.0}/form4api.egg-info/SOURCES.txt +0 -0
  21. {form4api-0.7.1 → form4api-0.8.0}/form4api.egg-info/dependency_links.txt +0 -0
  22. {form4api-0.7.1 → form4api-0.8.0}/form4api.egg-info/requires.txt +0 -0
  23. {form4api-0.7.1 → form4api-0.8.0}/form4api.egg-info/top_level.txt +0 -0
  24. {form4api-0.7.1 → form4api-0.8.0}/setup.cfg +0 -0
  25. {form4api-0.7.1 → form4api-0.8.0}/tests/test_generated.py +0 -0
  26. {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.7.1
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, and filed/notice dates. For a bulk historical pull use GET /v1/form144/export instead. Requires Business plan or higher (402 PLAN_REQUIRED on Free/Starter/Pro). Query runs live against the database — no caching."""
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, and filed/notice dates. For a bulk historical pull use GET /v1/form144/export instead. Requires Business plan or higher (402 PLAN_REQUIRED on Free/Starter/Pro). Query runs live against the database — no caching."""
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. Not plan-gated.
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. Not plan-gated.
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(**item) for item in data]
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(**item) for item in data]
87
+ return [Transaction._from_dict(item) for item in data]
88
88
 
89
89
 
90
90
 
@@ -1,4 +1,4 @@
1
- from __future__ import annotations
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
- Eighteen optional filters is exactly the kind of block that drifts when it
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), ("codes", codes),
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, exclude_10b5=exclude_10b5,
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(**item) for item in data]
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, exclude_10b5=exclude_10b5,
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(**item) for item in data]
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, exclude_10b5=exclude_10b5,
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.7.1
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.1"
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