form4api 0.3.0__tar.gz → 0.4.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 (25) hide show
  1. form4api-0.4.0/PKG-INFO +208 -0
  2. form4api-0.4.0/README.md +190 -0
  3. {form4api-0.3.0 → form4api-0.4.0}/form4api/_types.py +3 -0
  4. {form4api-0.3.0 → form4api-0.4.0}/form4api/resources/_transactions.py +47 -1
  5. form4api-0.4.0/form4api.egg-info/PKG-INFO +208 -0
  6. {form4api-0.3.0 → form4api-0.4.0}/pyproject.toml +27 -27
  7. {form4api-0.3.0 → form4api-0.4.0}/tests/test_client.py +32 -0
  8. form4api-0.3.0/PKG-INFO +0 -104
  9. form4api-0.3.0/README.md +0 -86
  10. form4api-0.3.0/form4api.egg-info/PKG-INFO +0 -104
  11. {form4api-0.3.0 → form4api-0.4.0}/LICENSE +0 -0
  12. {form4api-0.3.0 → form4api-0.4.0}/form4api/__init__.py +0 -0
  13. {form4api-0.3.0 → form4api-0.4.0}/form4api/_client.py +0 -0
  14. {form4api-0.3.0 → form4api-0.4.0}/form4api/_errors.py +0 -0
  15. {form4api-0.3.0 → form4api-0.4.0}/form4api/_webhook_utils.py +0 -0
  16. {form4api-0.3.0 → form4api-0.4.0}/form4api/resources/__init__.py +0 -0
  17. {form4api-0.3.0 → form4api-0.4.0}/form4api/resources/_companies.py +0 -0
  18. {form4api-0.3.0 → form4api-0.4.0}/form4api/resources/_insiders.py +0 -0
  19. {form4api-0.3.0 → form4api-0.4.0}/form4api/resources/_signals.py +0 -0
  20. {form4api-0.3.0 → form4api-0.4.0}/form4api/resources/_webhooks.py +0 -0
  21. {form4api-0.3.0 → form4api-0.4.0}/form4api.egg-info/SOURCES.txt +0 -0
  22. {form4api-0.3.0 → form4api-0.4.0}/form4api.egg-info/dependency_links.txt +0 -0
  23. {form4api-0.3.0 → form4api-0.4.0}/form4api.egg-info/requires.txt +0 -0
  24. {form4api-0.3.0 → form4api-0.4.0}/form4api.egg-info/top_level.txt +0 -0
  25. {form4api-0.3.0 → form4api-0.4.0}/setup.cfg +0 -0
@@ -0,0 +1,208 @@
1
+ Metadata-Version: 2.4
2
+ Name: form4api
3
+ Version: 0.4.0
4
+ Summary: Python client for the Form4API — real-time SEC Form 4 insider trading data
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://form4api.com
7
+ Project-URL: Documentation, https://form4api.com/docs
8
+ Project-URL: Repository, https://github.com/theodor90/form4api-py.git
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: httpx>=0.27
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=8; extra == "dev"
15
+ Requires-Dist: pytest-httpx>=0.30; extra == "dev"
16
+ Requires-Dist: respx>=0.21; extra == "dev"
17
+ Dynamic: license-file
18
+
19
+ # form4api
20
+
21
+ Python client for [Form4API](https://form4api.com) — real-time SEC Form 4 insider trading data.
22
+
23
+ Supports Python 3.11+. Uses `httpx` for both sync and async HTTP.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pip install form4api
29
+ ```
30
+
31
+ ## Sync quickstart
32
+
33
+ ```python
34
+ from form4api import Form4ApiClient
35
+
36
+ client = Form4ApiClient("YOUR_API_KEY")
37
+
38
+ # Recent open-market purchases at Apple (excluding 10b5-1 plan trades)
39
+ txns = client.transactions.list(ticker="AAPL", code="P", exclude_10b5=True, per_page=5)
40
+ for t in txns:
41
+ print(t.insider_name, t.insider_title, t.shares_amount, "@", t.price_per_share)
42
+ print(f" open market: {t.is_open_market}, 10b5 plan: {t.is10b5_plan}, value: ${t.total_value:,.0f}")
43
+
44
+ # Company overview (includes SIC, state, website)
45
+ company = client.companies.get("MSFT")
46
+ print(company.name, company.active_insiders, "active insiders")
47
+ print(company.sic_description, company.state_of_incorporation)
48
+
49
+ # Insider detail
50
+ insider = client.insiders.get("0001234567")
51
+ print(insider.name, insider.officer_title)
52
+
53
+ # Cluster-buy signals (Business plan)
54
+ signals = client.signals.list(cluster_buy=True)
55
+ for sig in signals:
56
+ print(sig.company_name, sig.insider_count, "buyers on", sig.signal_date)
57
+ ```
58
+
59
+ ## Async quickstart
60
+
61
+ ```python
62
+ import asyncio
63
+ from form4api import AsyncForm4ApiClient
64
+
65
+ async def main():
66
+ async with AsyncForm4ApiClient("YOUR_API_KEY") as client:
67
+ txns = await client.transactions.list(ticker="AAPL", per_page=5)
68
+ for t in txns:
69
+ print(t.insider_name, t.shares_amount, "@", t.price_per_share)
70
+
71
+ asyncio.run(main())
72
+ ```
73
+
74
+ ## Resources
75
+
76
+ | Resource | Methods |
77
+ |---|---|
78
+ | `client.transactions` | `.list(**params)`, `.paginate(**params)` |
79
+ | `client.insiders` | `.search(name, **params)`, `.get(cik)`, `.transactions(cik, **params)` |
80
+ | `client.companies` | `.get(ticker)`, `.insiders(ticker)` |
81
+ | `client.signals` | `.list(**params)`, `.paginate(**params)` — Business plan |
82
+ | `client.webhooks` | `.create(url, event_types)`, `.list()`, `.delete(id)`, `.events(**params)` |
83
+
84
+ ### Not yet in this SDK
85
+
86
+ The API surface is broader than the typed client. These backend features are **available via the REST API and the `form4api-mcp` server today, but don't have a typed SDK resource yet**:
87
+
88
+ - **Form 144** notice-of-proposed-sale — `GET /v1/form144` *(Business)*
89
+ - **Institutional holdings (13F-HR)** — `GET /v1/holdings`, **managers** — `GET /v1/managers` *(Business)*
90
+ - **Sentiment** (MSPR-style, 10b5-1-clean) — `GET /v1/signals/sentiment/{ticker}` *(Business)*
91
+ - **Insider career summary** — `GET /v1/insiders/{cik}/summary` *(Pro)*
92
+ - **Post-trade returns** (1d/1w/1m/3m/6m) + `min_return_*` screening filters on `/v1/transactions` *(visible free; screening Pro)*
93
+
94
+ Until they land in the SDK, call them directly (`client._get("/v1/holdings", {...})`) or see the [full REST reference](https://form4api.com/docs). For LLM workflows, `form4api-mcp` exposes all of the above as tools.
95
+
96
+ ### Transaction filters
97
+
98
+ ```python
99
+ client.transactions.list(
100
+ ticker="AAPL", # filter by ticker
101
+ cik="0000320193", # or by company CIK
102
+ insider_cik="...", # filter by insider CIK
103
+ code="P", # transaction code: P=purchase, S=sale, A=grant, etc.
104
+ from_date="2026-01-01",
105
+ to_date="2026-12-31",
106
+ exclude_10b5=True, # omit trades filed under a Rule 10b5-1 plan
107
+ per_page=100,
108
+ page=1,
109
+ )
110
+ ```
111
+
112
+ ### Granular filtering (v0.4.0+)
113
+
114
+ ```python
115
+ # The "just show me real buys & sells" preset: open-market only,
116
+ # no 10b5-1 plan trades, no derivatives.
117
+ client.transactions.list(ticker="AAPL", significant=True)
118
+
119
+ # Multi-code include / exclude (comma-separated SEC codes)
120
+ client.transactions.list(codes="P,S")
121
+ client.transactions.list(exclude_codes="A,M,F,G")
122
+
123
+ # Whole-category filters: open_market | grants | derivatives | gifts | other
124
+ client.transactions.list(category="open_market")
125
+ client.transactions.list(exclude_category="derivatives")
126
+ client.transactions.list(exclude_derivative=True)
127
+
128
+ # Trade-size screening (Pro plan or higher)
129
+ client.transactions.list(min_value=1_000_000) # USD, shares x price
130
+ client.transactions.list(min_shares=10_000, max_shares=100_000)
131
+ ```
132
+
133
+ ### Transaction fields
134
+
135
+ | Field | Type | Description |
136
+ |-------|------|-------------|
137
+ | `ticker` | `str` | Stock ticker |
138
+ | `company_name` | `str` | Company name |
139
+ | `insider_name` | `str` | Insider full name |
140
+ | `insider_cik` | `str` | Insider CIK |
141
+ | `insider_title` | `str \| None` | Officer title as reported on the Form 4 |
142
+ | `is_director` | `bool` | Director flag |
143
+ | `is_officer` | `bool` | Officer flag |
144
+ | `is10_pct_owner` | `bool` | 10% owner flag |
145
+ | `accession_number` | `str` | SEC accession number |
146
+ | `security_title` | `str` | Security type |
147
+ | `transaction_code` | `str` | Transaction code |
148
+ | `is_open_market` | `bool` | `True` when code is P or S (not grants/awards) |
149
+ | `is10b5_plan` | `bool` | Filed under a Rule 10b5-1 pre-scheduled trading plan |
150
+ | `shares_amount` | `float` | Shares transacted |
151
+ | `price_per_share` | `float \| None` | Price per share |
152
+ | `total_value` | `float \| None` | `shares_amount × price_per_share` in USD |
153
+ | `shares_owned_after` | `float \| None` | Holdings after transaction |
154
+ | `direct_indirect` | `str \| None` | "D" (direct) or "I" (indirect) |
155
+ | `is_derivative` | `bool` | Derivative security flag |
156
+ | `transaction_date` | `str` | ISO datetime |
157
+ | `period_of_report` | `str` | ISO datetime |
158
+
159
+ ### Company fields
160
+
161
+ | Field | Type | Description |
162
+ |-------|------|-------------|
163
+ | `cik` | `str` | SEC CIK |
164
+ | `name` | `str` | Company name |
165
+ | `ticker` | `str \| None` | Stock ticker |
166
+ | `exchange` | `str \| None` | Exchange |
167
+ | `total_filings` | `int` | Total Form 4 filings |
168
+ | `active_insiders` | `int` | Distinct insiders who have filed |
169
+ | `sic_description` | `str \| None` | SEC SIC industry description |
170
+ | `state_of_incorporation` | `str \| None` | Two-letter state code |
171
+ | `website` | `str \| None` | Company website as filed with SEC |
172
+
173
+ ### Pagination
174
+
175
+ ```python
176
+ # transactions.paginate() — yields one list per page automatically
177
+ all_txns = []
178
+ for batch in client.transactions.paginate(ticker="NVDA", exclude_10b5=True, per_page=500):
179
+ all_txns.extend(batch)
180
+
181
+ # signals.paginate()
182
+ all_signals = []
183
+ for batch in client.signals.paginate(cluster_buy=True, per_page=100):
184
+ all_signals.extend(batch)
185
+ ```
186
+
187
+ ## Error handling
188
+
189
+ ```python
190
+ from form4api import Form4ApiClient, AuthError, PlanError, RateLimitError, NotFoundError
191
+
192
+ client = Form4ApiClient("YOUR_API_KEY")
193
+
194
+ try:
195
+ signals = client.signals.list()
196
+ except PlanError as e:
197
+ print(f"Upgrade required")
198
+ except RateLimitError as e:
199
+ print(f"Retry after {e.retry_after}s")
200
+ except AuthError:
201
+ print("Invalid API key")
202
+ except NotFoundError:
203
+ print("Resource not found")
204
+ ```
205
+
206
+ ## License
207
+
208
+ MIT
@@ -0,0 +1,190 @@
1
+ # form4api
2
+
3
+ Python client for [Form4API](https://form4api.com) — real-time SEC Form 4 insider trading data.
4
+
5
+ Supports Python 3.11+. Uses `httpx` for both sync and async HTTP.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install form4api
11
+ ```
12
+
13
+ ## Sync quickstart
14
+
15
+ ```python
16
+ from form4api import Form4ApiClient
17
+
18
+ client = Form4ApiClient("YOUR_API_KEY")
19
+
20
+ # Recent open-market purchases at Apple (excluding 10b5-1 plan trades)
21
+ txns = client.transactions.list(ticker="AAPL", code="P", exclude_10b5=True, per_page=5)
22
+ for t in txns:
23
+ print(t.insider_name, t.insider_title, t.shares_amount, "@", t.price_per_share)
24
+ print(f" open market: {t.is_open_market}, 10b5 plan: {t.is10b5_plan}, value: ${t.total_value:,.0f}")
25
+
26
+ # Company overview (includes SIC, state, website)
27
+ company = client.companies.get("MSFT")
28
+ print(company.name, company.active_insiders, "active insiders")
29
+ print(company.sic_description, company.state_of_incorporation)
30
+
31
+ # Insider detail
32
+ insider = client.insiders.get("0001234567")
33
+ print(insider.name, insider.officer_title)
34
+
35
+ # Cluster-buy signals (Business plan)
36
+ signals = client.signals.list(cluster_buy=True)
37
+ for sig in signals:
38
+ print(sig.company_name, sig.insider_count, "buyers on", sig.signal_date)
39
+ ```
40
+
41
+ ## Async quickstart
42
+
43
+ ```python
44
+ import asyncio
45
+ from form4api import AsyncForm4ApiClient
46
+
47
+ async def main():
48
+ async with AsyncForm4ApiClient("YOUR_API_KEY") as client:
49
+ txns = await client.transactions.list(ticker="AAPL", per_page=5)
50
+ for t in txns:
51
+ print(t.insider_name, t.shares_amount, "@", t.price_per_share)
52
+
53
+ asyncio.run(main())
54
+ ```
55
+
56
+ ## Resources
57
+
58
+ | Resource | Methods |
59
+ |---|---|
60
+ | `client.transactions` | `.list(**params)`, `.paginate(**params)` |
61
+ | `client.insiders` | `.search(name, **params)`, `.get(cik)`, `.transactions(cik, **params)` |
62
+ | `client.companies` | `.get(ticker)`, `.insiders(ticker)` |
63
+ | `client.signals` | `.list(**params)`, `.paginate(**params)` — Business plan |
64
+ | `client.webhooks` | `.create(url, event_types)`, `.list()`, `.delete(id)`, `.events(**params)` |
65
+
66
+ ### Not yet in this SDK
67
+
68
+ The API surface is broader than the typed client. These backend features are **available via the REST API and the `form4api-mcp` server today, but don't have a typed SDK resource yet**:
69
+
70
+ - **Form 144** notice-of-proposed-sale — `GET /v1/form144` *(Business)*
71
+ - **Institutional holdings (13F-HR)** — `GET /v1/holdings`, **managers** — `GET /v1/managers` *(Business)*
72
+ - **Sentiment** (MSPR-style, 10b5-1-clean) — `GET /v1/signals/sentiment/{ticker}` *(Business)*
73
+ - **Insider career summary** — `GET /v1/insiders/{cik}/summary` *(Pro)*
74
+ - **Post-trade returns** (1d/1w/1m/3m/6m) + `min_return_*` screening filters on `/v1/transactions` *(visible free; screening Pro)*
75
+
76
+ Until they land in the SDK, call them directly (`client._get("/v1/holdings", {...})`) or see the [full REST reference](https://form4api.com/docs). For LLM workflows, `form4api-mcp` exposes all of the above as tools.
77
+
78
+ ### Transaction filters
79
+
80
+ ```python
81
+ client.transactions.list(
82
+ ticker="AAPL", # filter by ticker
83
+ cik="0000320193", # or by company CIK
84
+ insider_cik="...", # filter by insider CIK
85
+ code="P", # transaction code: P=purchase, S=sale, A=grant, etc.
86
+ from_date="2026-01-01",
87
+ to_date="2026-12-31",
88
+ exclude_10b5=True, # omit trades filed under a Rule 10b5-1 plan
89
+ per_page=100,
90
+ page=1,
91
+ )
92
+ ```
93
+
94
+ ### Granular filtering (v0.4.0+)
95
+
96
+ ```python
97
+ # The "just show me real buys & sells" preset: open-market only,
98
+ # no 10b5-1 plan trades, no derivatives.
99
+ client.transactions.list(ticker="AAPL", significant=True)
100
+
101
+ # Multi-code include / exclude (comma-separated SEC codes)
102
+ client.transactions.list(codes="P,S")
103
+ client.transactions.list(exclude_codes="A,M,F,G")
104
+
105
+ # Whole-category filters: open_market | grants | derivatives | gifts | other
106
+ client.transactions.list(category="open_market")
107
+ client.transactions.list(exclude_category="derivatives")
108
+ client.transactions.list(exclude_derivative=True)
109
+
110
+ # Trade-size screening (Pro plan or higher)
111
+ client.transactions.list(min_value=1_000_000) # USD, shares x price
112
+ client.transactions.list(min_shares=10_000, max_shares=100_000)
113
+ ```
114
+
115
+ ### Transaction fields
116
+
117
+ | Field | Type | Description |
118
+ |-------|------|-------------|
119
+ | `ticker` | `str` | Stock ticker |
120
+ | `company_name` | `str` | Company name |
121
+ | `insider_name` | `str` | Insider full name |
122
+ | `insider_cik` | `str` | Insider CIK |
123
+ | `insider_title` | `str \| None` | Officer title as reported on the Form 4 |
124
+ | `is_director` | `bool` | Director flag |
125
+ | `is_officer` | `bool` | Officer flag |
126
+ | `is10_pct_owner` | `bool` | 10% owner flag |
127
+ | `accession_number` | `str` | SEC accession number |
128
+ | `security_title` | `str` | Security type |
129
+ | `transaction_code` | `str` | Transaction code |
130
+ | `is_open_market` | `bool` | `True` when code is P or S (not grants/awards) |
131
+ | `is10b5_plan` | `bool` | Filed under a Rule 10b5-1 pre-scheduled trading plan |
132
+ | `shares_amount` | `float` | Shares transacted |
133
+ | `price_per_share` | `float \| None` | Price per share |
134
+ | `total_value` | `float \| None` | `shares_amount × price_per_share` in USD |
135
+ | `shares_owned_after` | `float \| None` | Holdings after transaction |
136
+ | `direct_indirect` | `str \| None` | "D" (direct) or "I" (indirect) |
137
+ | `is_derivative` | `bool` | Derivative security flag |
138
+ | `transaction_date` | `str` | ISO datetime |
139
+ | `period_of_report` | `str` | ISO datetime |
140
+
141
+ ### Company fields
142
+
143
+ | Field | Type | Description |
144
+ |-------|------|-------------|
145
+ | `cik` | `str` | SEC CIK |
146
+ | `name` | `str` | Company name |
147
+ | `ticker` | `str \| None` | Stock ticker |
148
+ | `exchange` | `str \| None` | Exchange |
149
+ | `total_filings` | `int` | Total Form 4 filings |
150
+ | `active_insiders` | `int` | Distinct insiders who have filed |
151
+ | `sic_description` | `str \| None` | SEC SIC industry description |
152
+ | `state_of_incorporation` | `str \| None` | Two-letter state code |
153
+ | `website` | `str \| None` | Company website as filed with SEC |
154
+
155
+ ### Pagination
156
+
157
+ ```python
158
+ # transactions.paginate() — yields one list per page automatically
159
+ all_txns = []
160
+ for batch in client.transactions.paginate(ticker="NVDA", exclude_10b5=True, per_page=500):
161
+ all_txns.extend(batch)
162
+
163
+ # signals.paginate()
164
+ all_signals = []
165
+ for batch in client.signals.paginate(cluster_buy=True, per_page=100):
166
+ all_signals.extend(batch)
167
+ ```
168
+
169
+ ## Error handling
170
+
171
+ ```python
172
+ from form4api import Form4ApiClient, AuthError, PlanError, RateLimitError, NotFoundError
173
+
174
+ client = Form4ApiClient("YOUR_API_KEY")
175
+
176
+ try:
177
+ signals = client.signals.list()
178
+ except PlanError as e:
179
+ print(f"Upgrade required")
180
+ except RateLimitError as e:
181
+ print(f"Retry after {e.retry_after}s")
182
+ except AuthError:
183
+ print("Invalid API key")
184
+ except NotFoundError:
185
+ print("Resource not found")
186
+ ```
187
+
188
+ ## License
189
+
190
+ MIT
@@ -47,6 +47,9 @@ class Company:
47
47
  exchange: str | None
48
48
  total_filings: int
49
49
  active_insiders: int
50
+ sic_description: str | None
51
+ state_of_incorporation: str | None
52
+ website: str | None
50
53
 
51
54
 
52
55
  @dataclass
@@ -23,6 +23,16 @@ class TransactionsResource:
23
23
  from_date: str | None = None,
24
24
  to_date: str | None = None,
25
25
  exclude_10b5: bool | None = None,
26
+ codes: str | None = None,
27
+ exclude_codes: str | None = None,
28
+ category: str | None = None,
29
+ exclude_category: str | None = None,
30
+ exclude_derivative: bool | None = None,
31
+ significant: bool | None = None,
32
+ min_value: float | None = None,
33
+ max_value: float | None = None,
34
+ min_shares: float | None = None,
35
+ max_shares: float | None = None,
26
36
  page: int = 1,
27
37
  per_page: int = 50,
28
38
  ) -> list[Transaction]:
@@ -41,6 +51,26 @@ class TransactionsResource:
41
51
  params["to"] = to_date
42
52
  if exclude_10b5 is not None:
43
53
  params["exclude_10b5"] = str(exclude_10b5).lower()
54
+ if codes is not None:
55
+ params["codes"] = codes
56
+ if exclude_codes is not None:
57
+ params["exclude_codes"] = exclude_codes
58
+ if category is not None:
59
+ params["category"] = category
60
+ if exclude_category is not None:
61
+ params["exclude_category"] = exclude_category
62
+ if exclude_derivative is not None:
63
+ params["exclude_derivative"] = str(exclude_derivative).lower()
64
+ if significant is not None:
65
+ params["significant"] = str(significant).lower()
66
+ if min_value is not None:
67
+ params["min_value"] = str(min_value)
68
+ if max_value is not None:
69
+ params["max_value"] = str(max_value)
70
+ if min_shares is not None:
71
+ params["min_shares"] = str(min_shares)
72
+ if max_shares is not None:
73
+ params["max_shares"] = str(max_shares)
44
74
  data = self._client._get("/v1/transactions", params)
45
75
  return [Transaction(**item) for item in data]
46
76
 
@@ -54,6 +84,16 @@ class TransactionsResource:
54
84
  from_date: str | None = None,
55
85
  to_date: str | None = None,
56
86
  exclude_10b5: bool | None = None,
87
+ codes: str | None = None,
88
+ exclude_codes: str | None = None,
89
+ category: str | None = None,
90
+ exclude_category: str | None = None,
91
+ exclude_derivative: bool | None = None,
92
+ significant: bool | None = None,
93
+ min_value: float | None = None,
94
+ max_value: float | None = None,
95
+ min_shares: float | None = None,
96
+ max_shares: float | None = None,
57
97
  per_page: int = 50,
58
98
  ) -> Generator[list[Transaction], None, None]:
59
99
  page = 1
@@ -61,7 +101,13 @@ class TransactionsResource:
61
101
  batch = self.list(
62
102
  ticker=ticker, cik=cik, insider_cik=insider_cik,
63
103
  code=code, from_date=from_date, to_date=to_date,
64
- exclude_10b5=exclude_10b5, page=page, per_page=per_page,
104
+ exclude_10b5=exclude_10b5,
105
+ codes=codes, exclude_codes=exclude_codes,
106
+ category=category, exclude_category=exclude_category,
107
+ exclude_derivative=exclude_derivative, significant=significant,
108
+ min_value=min_value, max_value=max_value,
109
+ min_shares=min_shares, max_shares=max_shares,
110
+ page=page, per_page=per_page,
65
111
  )
66
112
  if not batch:
67
113
  break
@@ -0,0 +1,208 @@
1
+ Metadata-Version: 2.4
2
+ Name: form4api
3
+ Version: 0.4.0
4
+ Summary: Python client for the Form4API — real-time SEC Form 4 insider trading data
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://form4api.com
7
+ Project-URL: Documentation, https://form4api.com/docs
8
+ Project-URL: Repository, https://github.com/theodor90/form4api-py.git
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: httpx>=0.27
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=8; extra == "dev"
15
+ Requires-Dist: pytest-httpx>=0.30; extra == "dev"
16
+ Requires-Dist: respx>=0.21; extra == "dev"
17
+ Dynamic: license-file
18
+
19
+ # form4api
20
+
21
+ Python client for [Form4API](https://form4api.com) — real-time SEC Form 4 insider trading data.
22
+
23
+ Supports Python 3.11+. Uses `httpx` for both sync and async HTTP.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pip install form4api
29
+ ```
30
+
31
+ ## Sync quickstart
32
+
33
+ ```python
34
+ from form4api import Form4ApiClient
35
+
36
+ client = Form4ApiClient("YOUR_API_KEY")
37
+
38
+ # Recent open-market purchases at Apple (excluding 10b5-1 plan trades)
39
+ txns = client.transactions.list(ticker="AAPL", code="P", exclude_10b5=True, per_page=5)
40
+ for t in txns:
41
+ print(t.insider_name, t.insider_title, t.shares_amount, "@", t.price_per_share)
42
+ print(f" open market: {t.is_open_market}, 10b5 plan: {t.is10b5_plan}, value: ${t.total_value:,.0f}")
43
+
44
+ # Company overview (includes SIC, state, website)
45
+ company = client.companies.get("MSFT")
46
+ print(company.name, company.active_insiders, "active insiders")
47
+ print(company.sic_description, company.state_of_incorporation)
48
+
49
+ # Insider detail
50
+ insider = client.insiders.get("0001234567")
51
+ print(insider.name, insider.officer_title)
52
+
53
+ # Cluster-buy signals (Business plan)
54
+ signals = client.signals.list(cluster_buy=True)
55
+ for sig in signals:
56
+ print(sig.company_name, sig.insider_count, "buyers on", sig.signal_date)
57
+ ```
58
+
59
+ ## Async quickstart
60
+
61
+ ```python
62
+ import asyncio
63
+ from form4api import AsyncForm4ApiClient
64
+
65
+ async def main():
66
+ async with AsyncForm4ApiClient("YOUR_API_KEY") as client:
67
+ txns = await client.transactions.list(ticker="AAPL", per_page=5)
68
+ for t in txns:
69
+ print(t.insider_name, t.shares_amount, "@", t.price_per_share)
70
+
71
+ asyncio.run(main())
72
+ ```
73
+
74
+ ## Resources
75
+
76
+ | Resource | Methods |
77
+ |---|---|
78
+ | `client.transactions` | `.list(**params)`, `.paginate(**params)` |
79
+ | `client.insiders` | `.search(name, **params)`, `.get(cik)`, `.transactions(cik, **params)` |
80
+ | `client.companies` | `.get(ticker)`, `.insiders(ticker)` |
81
+ | `client.signals` | `.list(**params)`, `.paginate(**params)` — Business plan |
82
+ | `client.webhooks` | `.create(url, event_types)`, `.list()`, `.delete(id)`, `.events(**params)` |
83
+
84
+ ### Not yet in this SDK
85
+
86
+ The API surface is broader than the typed client. These backend features are **available via the REST API and the `form4api-mcp` server today, but don't have a typed SDK resource yet**:
87
+
88
+ - **Form 144** notice-of-proposed-sale — `GET /v1/form144` *(Business)*
89
+ - **Institutional holdings (13F-HR)** — `GET /v1/holdings`, **managers** — `GET /v1/managers` *(Business)*
90
+ - **Sentiment** (MSPR-style, 10b5-1-clean) — `GET /v1/signals/sentiment/{ticker}` *(Business)*
91
+ - **Insider career summary** — `GET /v1/insiders/{cik}/summary` *(Pro)*
92
+ - **Post-trade returns** (1d/1w/1m/3m/6m) + `min_return_*` screening filters on `/v1/transactions` *(visible free; screening Pro)*
93
+
94
+ Until they land in the SDK, call them directly (`client._get("/v1/holdings", {...})`) or see the [full REST reference](https://form4api.com/docs). For LLM workflows, `form4api-mcp` exposes all of the above as tools.
95
+
96
+ ### Transaction filters
97
+
98
+ ```python
99
+ client.transactions.list(
100
+ ticker="AAPL", # filter by ticker
101
+ cik="0000320193", # or by company CIK
102
+ insider_cik="...", # filter by insider CIK
103
+ code="P", # transaction code: P=purchase, S=sale, A=grant, etc.
104
+ from_date="2026-01-01",
105
+ to_date="2026-12-31",
106
+ exclude_10b5=True, # omit trades filed under a Rule 10b5-1 plan
107
+ per_page=100,
108
+ page=1,
109
+ )
110
+ ```
111
+
112
+ ### Granular filtering (v0.4.0+)
113
+
114
+ ```python
115
+ # The "just show me real buys & sells" preset: open-market only,
116
+ # no 10b5-1 plan trades, no derivatives.
117
+ client.transactions.list(ticker="AAPL", significant=True)
118
+
119
+ # Multi-code include / exclude (comma-separated SEC codes)
120
+ client.transactions.list(codes="P,S")
121
+ client.transactions.list(exclude_codes="A,M,F,G")
122
+
123
+ # Whole-category filters: open_market | grants | derivatives | gifts | other
124
+ client.transactions.list(category="open_market")
125
+ client.transactions.list(exclude_category="derivatives")
126
+ client.transactions.list(exclude_derivative=True)
127
+
128
+ # Trade-size screening (Pro plan or higher)
129
+ client.transactions.list(min_value=1_000_000) # USD, shares x price
130
+ client.transactions.list(min_shares=10_000, max_shares=100_000)
131
+ ```
132
+
133
+ ### Transaction fields
134
+
135
+ | Field | Type | Description |
136
+ |-------|------|-------------|
137
+ | `ticker` | `str` | Stock ticker |
138
+ | `company_name` | `str` | Company name |
139
+ | `insider_name` | `str` | Insider full name |
140
+ | `insider_cik` | `str` | Insider CIK |
141
+ | `insider_title` | `str \| None` | Officer title as reported on the Form 4 |
142
+ | `is_director` | `bool` | Director flag |
143
+ | `is_officer` | `bool` | Officer flag |
144
+ | `is10_pct_owner` | `bool` | 10% owner flag |
145
+ | `accession_number` | `str` | SEC accession number |
146
+ | `security_title` | `str` | Security type |
147
+ | `transaction_code` | `str` | Transaction code |
148
+ | `is_open_market` | `bool` | `True` when code is P or S (not grants/awards) |
149
+ | `is10b5_plan` | `bool` | Filed under a Rule 10b5-1 pre-scheduled trading plan |
150
+ | `shares_amount` | `float` | Shares transacted |
151
+ | `price_per_share` | `float \| None` | Price per share |
152
+ | `total_value` | `float \| None` | `shares_amount × price_per_share` in USD |
153
+ | `shares_owned_after` | `float \| None` | Holdings after transaction |
154
+ | `direct_indirect` | `str \| None` | "D" (direct) or "I" (indirect) |
155
+ | `is_derivative` | `bool` | Derivative security flag |
156
+ | `transaction_date` | `str` | ISO datetime |
157
+ | `period_of_report` | `str` | ISO datetime |
158
+
159
+ ### Company fields
160
+
161
+ | Field | Type | Description |
162
+ |-------|------|-------------|
163
+ | `cik` | `str` | SEC CIK |
164
+ | `name` | `str` | Company name |
165
+ | `ticker` | `str \| None` | Stock ticker |
166
+ | `exchange` | `str \| None` | Exchange |
167
+ | `total_filings` | `int` | Total Form 4 filings |
168
+ | `active_insiders` | `int` | Distinct insiders who have filed |
169
+ | `sic_description` | `str \| None` | SEC SIC industry description |
170
+ | `state_of_incorporation` | `str \| None` | Two-letter state code |
171
+ | `website` | `str \| None` | Company website as filed with SEC |
172
+
173
+ ### Pagination
174
+
175
+ ```python
176
+ # transactions.paginate() — yields one list per page automatically
177
+ all_txns = []
178
+ for batch in client.transactions.paginate(ticker="NVDA", exclude_10b5=True, per_page=500):
179
+ all_txns.extend(batch)
180
+
181
+ # signals.paginate()
182
+ all_signals = []
183
+ for batch in client.signals.paginate(cluster_buy=True, per_page=100):
184
+ all_signals.extend(batch)
185
+ ```
186
+
187
+ ## Error handling
188
+
189
+ ```python
190
+ from form4api import Form4ApiClient, AuthError, PlanError, RateLimitError, NotFoundError
191
+
192
+ client = Form4ApiClient("YOUR_API_KEY")
193
+
194
+ try:
195
+ signals = client.signals.list()
196
+ except PlanError as e:
197
+ print(f"Upgrade required")
198
+ except RateLimitError as e:
199
+ print(f"Retry after {e.retry_after}s")
200
+ except AuthError:
201
+ print("Invalid API key")
202
+ except NotFoundError:
203
+ print("Resource not found")
204
+ ```
205
+
206
+ ## License
207
+
208
+ MIT
@@ -1,27 +1,27 @@
1
- [build-system]
2
- requires = ["setuptools>=68", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "form4api"
7
- version = "0.3.0"
8
- description = "Python client for the Form4API — real-time SEC Form 4 insider trading data"
9
- requires-python = ">=3.11"
10
- dependencies = ["httpx>=0.27"]
11
- license = "MIT"
12
- readme = "README.md"
13
-
14
- [project.urls]
15
- Homepage = "https://form4api.com"
16
- Documentation = "https://form4api.com/docs"
17
- Repository = "https://github.com/theodor90/form4api-py.git"
18
-
19
- [project.optional-dependencies]
20
- dev = ["pytest>=8", "pytest-httpx>=0.30", "respx>=0.21"]
21
-
22
- [tool.setuptools.packages.find]
23
- where = ["."]
24
- include = ["form4api*"]
25
-
26
- [tool.pytest.ini_options]
27
- testpaths = ["tests"]
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "form4api"
7
+ version = "0.4.0"
8
+ description = "Python client for the Form4API — real-time SEC Form 4 insider trading data"
9
+ requires-python = ">=3.11"
10
+ dependencies = ["httpx>=0.27"]
11
+ license = "MIT"
12
+ readme = "README.md"
13
+
14
+ [project.urls]
15
+ Homepage = "https://form4api.com"
16
+ Documentation = "https://form4api.com/docs"
17
+ Repository = "https://github.com/theodor90/form4api-py.git"
18
+
19
+ [project.optional-dependencies]
20
+ dev = ["pytest>=8", "pytest-httpx>=0.30", "respx>=0.21"]
21
+
22
+ [tool.setuptools.packages.find]
23
+ where = ["."]
24
+ include = ["form4api*"]
25
+
26
+ [tool.pytest.ini_options]
27
+ testpaths = ["tests"]
@@ -59,6 +59,9 @@ COMPANY = {
59
59
  "exchange": "NASDAQ",
60
60
  "totalFilings": 100,
61
61
  "activeInsiders": 12,
62
+ "sicDescription": "Electronic Computers",
63
+ "stateOfIncorporation": "CA",
64
+ "website": None,
62
65
  }
63
66
 
64
67
  SIGNAL = {
@@ -104,6 +107,35 @@ def test_transactions_list_sends_filters(client):
104
107
  assert qs["per_page"] == "10"
105
108
 
106
109
 
110
+ @respx.mock
111
+ def test_transactions_list_sends_granular_filters(client):
112
+ route = respx.get(f"{BASE}/v1/transactions").mock(return_value=httpx.Response(200, json=[]))
113
+ client.transactions.list(
114
+ codes="P,S",
115
+ exclude_codes="A,M",
116
+ category="open_market",
117
+ exclude_category="derivatives",
118
+ exclude_derivative=True,
119
+ significant=True,
120
+ min_value=100000,
121
+ max_value=5000000,
122
+ min_shares=100,
123
+ max_shares=10000,
124
+ )
125
+ assert route.called
126
+ qs = dict(route.calls[0].request.url.params)
127
+ assert qs["codes"] == "P,S"
128
+ assert qs["exclude_codes"] == "A,M"
129
+ assert qs["category"] == "open_market"
130
+ assert qs["exclude_category"] == "derivatives"
131
+ assert qs["exclude_derivative"] == "true"
132
+ assert qs["significant"] == "true"
133
+ assert qs["min_value"] == "100000"
134
+ assert qs["max_value"] == "5000000"
135
+ assert qs["min_shares"] == "100"
136
+ assert qs["max_shares"] == "10000"
137
+
138
+
107
139
  @respx.mock
108
140
  def test_transactions_paginate_stops_on_short_page(client):
109
141
  respx.get(f"{BASE}/v1/transactions").mock(side_effect=[
form4api-0.3.0/PKG-INFO DELETED
@@ -1,104 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: form4api
3
- Version: 0.3.0
4
- Summary: Python client for the Form4API — real-time SEC Form 4 insider trading data
5
- License-Expression: MIT
6
- Project-URL: Homepage, https://form4api.com
7
- Project-URL: Documentation, https://form4api.com/docs
8
- Project-URL: Repository, https://github.com/theodor90/form4api-py.git
9
- Requires-Python: >=3.11
10
- Description-Content-Type: text/markdown
11
- License-File: LICENSE
12
- Requires-Dist: httpx>=0.27
13
- Provides-Extra: dev
14
- Requires-Dist: pytest>=8; extra == "dev"
15
- Requires-Dist: pytest-httpx>=0.30; extra == "dev"
16
- Requires-Dist: respx>=0.21; extra == "dev"
17
- Dynamic: license-file
18
-
19
- # form4api
20
-
21
- Python client for [Form4API](https://form4api.com) — real-time SEC Form 4 insider trading data.
22
-
23
- Supports Python 3.11+. Uses `httpx` for both sync and async HTTP.
24
-
25
- ## Installation
26
-
27
- ```bash
28
- pip install form4api
29
- ```
30
-
31
- ## Sync quickstart
32
-
33
- ```python
34
- from form4api import Form4ApiClient
35
-
36
- client = Form4ApiClient("YOUR_API_KEY")
37
-
38
- # Recent open-market purchases at Apple
39
- txns = client.transactions.list(ticker="AAPL", code="P", per_page=5)
40
- for t in txns:
41
- print(t.insider_name, t.shares_amount, "@", t.price_per_share)
42
-
43
- # Company overview
44
- company = client.companies.get("MSFT")
45
- print(company.name, company.active_insiders, "active insiders")
46
-
47
- # Insider detail
48
- insider = client.insiders.get("0001234567")
49
- print(insider.name, insider.officer_title)
50
-
51
- # Cluster-buy signals (Business plan)
52
- signals = client.signals.list(ticker="NVDA")
53
- for sig in signals:
54
- if sig.is_cluster_buy:
55
- print(sig.company_name, sig.insider_count, "buyers")
56
- ```
57
-
58
- ## Async quickstart
59
-
60
- ```python
61
- import asyncio
62
- from form4api import AsyncForm4ApiClient
63
-
64
- async def main():
65
- async with AsyncForm4ApiClient("YOUR_API_KEY") as client:
66
- txns = await client.transactions.list(ticker="AAPL", per_page=5)
67
- for t in txns:
68
- print(t.insider_name, t.shares_amount, "@", t.price_per_share)
69
-
70
- asyncio.run(main())
71
- ```
72
-
73
- ## Resources
74
-
75
- | Resource | Methods |
76
- |---|---|
77
- | `client.transactions` | `.list(**params)`, `.paginate(**params)` |
78
- | `client.insiders` | `.search(name, **params)`, `.get(cik)`, `.transactions(cik, **params)` |
79
- | `client.companies` | `.get(ticker)`, `.insiders(ticker)` |
80
- | `client.signals` | `.list(**params)` — Business plan |
81
- | `client.webhooks` | `.create(url, event_types)`, `.list()`, `.delete(id)`, `.events(**params)` |
82
-
83
- ## Error handling
84
-
85
- ```python
86
- from form4api import Form4ApiClient, AuthError, PlanError, RateLimitError, NotFoundError
87
-
88
- client = Form4ApiClient("YOUR_API_KEY")
89
-
90
- try:
91
- signals = client.signals.list()
92
- except PlanError as e:
93
- print(f"Upgrade to {e.required_plan}")
94
- except RateLimitError as e:
95
- print(f"Retry after {e.retry_after}s")
96
- except AuthError:
97
- print("Invalid API key")
98
- except NotFoundError:
99
- print("Resource not found")
100
- ```
101
-
102
- ## License
103
-
104
- MIT
form4api-0.3.0/README.md DELETED
@@ -1,86 +0,0 @@
1
- # form4api
2
-
3
- Python client for [Form4API](https://form4api.com) — real-time SEC Form 4 insider trading data.
4
-
5
- Supports Python 3.11+. Uses `httpx` for both sync and async HTTP.
6
-
7
- ## Installation
8
-
9
- ```bash
10
- pip install form4api
11
- ```
12
-
13
- ## Sync quickstart
14
-
15
- ```python
16
- from form4api import Form4ApiClient
17
-
18
- client = Form4ApiClient("YOUR_API_KEY")
19
-
20
- # Recent open-market purchases at Apple
21
- txns = client.transactions.list(ticker="AAPL", code="P", per_page=5)
22
- for t in txns:
23
- print(t.insider_name, t.shares_amount, "@", t.price_per_share)
24
-
25
- # Company overview
26
- company = client.companies.get("MSFT")
27
- print(company.name, company.active_insiders, "active insiders")
28
-
29
- # Insider detail
30
- insider = client.insiders.get("0001234567")
31
- print(insider.name, insider.officer_title)
32
-
33
- # Cluster-buy signals (Business plan)
34
- signals = client.signals.list(ticker="NVDA")
35
- for sig in signals:
36
- if sig.is_cluster_buy:
37
- print(sig.company_name, sig.insider_count, "buyers")
38
- ```
39
-
40
- ## Async quickstart
41
-
42
- ```python
43
- import asyncio
44
- from form4api import AsyncForm4ApiClient
45
-
46
- async def main():
47
- async with AsyncForm4ApiClient("YOUR_API_KEY") as client:
48
- txns = await client.transactions.list(ticker="AAPL", per_page=5)
49
- for t in txns:
50
- print(t.insider_name, t.shares_amount, "@", t.price_per_share)
51
-
52
- asyncio.run(main())
53
- ```
54
-
55
- ## Resources
56
-
57
- | Resource | Methods |
58
- |---|---|
59
- | `client.transactions` | `.list(**params)`, `.paginate(**params)` |
60
- | `client.insiders` | `.search(name, **params)`, `.get(cik)`, `.transactions(cik, **params)` |
61
- | `client.companies` | `.get(ticker)`, `.insiders(ticker)` |
62
- | `client.signals` | `.list(**params)` — Business plan |
63
- | `client.webhooks` | `.create(url, event_types)`, `.list()`, `.delete(id)`, `.events(**params)` |
64
-
65
- ## Error handling
66
-
67
- ```python
68
- from form4api import Form4ApiClient, AuthError, PlanError, RateLimitError, NotFoundError
69
-
70
- client = Form4ApiClient("YOUR_API_KEY")
71
-
72
- try:
73
- signals = client.signals.list()
74
- except PlanError as e:
75
- print(f"Upgrade to {e.required_plan}")
76
- except RateLimitError as e:
77
- print(f"Retry after {e.retry_after}s")
78
- except AuthError:
79
- print("Invalid API key")
80
- except NotFoundError:
81
- print("Resource not found")
82
- ```
83
-
84
- ## License
85
-
86
- MIT
@@ -1,104 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: form4api
3
- Version: 0.3.0
4
- Summary: Python client for the Form4API — real-time SEC Form 4 insider trading data
5
- License-Expression: MIT
6
- Project-URL: Homepage, https://form4api.com
7
- Project-URL: Documentation, https://form4api.com/docs
8
- Project-URL: Repository, https://github.com/theodor90/form4api-py.git
9
- Requires-Python: >=3.11
10
- Description-Content-Type: text/markdown
11
- License-File: LICENSE
12
- Requires-Dist: httpx>=0.27
13
- Provides-Extra: dev
14
- Requires-Dist: pytest>=8; extra == "dev"
15
- Requires-Dist: pytest-httpx>=0.30; extra == "dev"
16
- Requires-Dist: respx>=0.21; extra == "dev"
17
- Dynamic: license-file
18
-
19
- # form4api
20
-
21
- Python client for [Form4API](https://form4api.com) — real-time SEC Form 4 insider trading data.
22
-
23
- Supports Python 3.11+. Uses `httpx` for both sync and async HTTP.
24
-
25
- ## Installation
26
-
27
- ```bash
28
- pip install form4api
29
- ```
30
-
31
- ## Sync quickstart
32
-
33
- ```python
34
- from form4api import Form4ApiClient
35
-
36
- client = Form4ApiClient("YOUR_API_KEY")
37
-
38
- # Recent open-market purchases at Apple
39
- txns = client.transactions.list(ticker="AAPL", code="P", per_page=5)
40
- for t in txns:
41
- print(t.insider_name, t.shares_amount, "@", t.price_per_share)
42
-
43
- # Company overview
44
- company = client.companies.get("MSFT")
45
- print(company.name, company.active_insiders, "active insiders")
46
-
47
- # Insider detail
48
- insider = client.insiders.get("0001234567")
49
- print(insider.name, insider.officer_title)
50
-
51
- # Cluster-buy signals (Business plan)
52
- signals = client.signals.list(ticker="NVDA")
53
- for sig in signals:
54
- if sig.is_cluster_buy:
55
- print(sig.company_name, sig.insider_count, "buyers")
56
- ```
57
-
58
- ## Async quickstart
59
-
60
- ```python
61
- import asyncio
62
- from form4api import AsyncForm4ApiClient
63
-
64
- async def main():
65
- async with AsyncForm4ApiClient("YOUR_API_KEY") as client:
66
- txns = await client.transactions.list(ticker="AAPL", per_page=5)
67
- for t in txns:
68
- print(t.insider_name, t.shares_amount, "@", t.price_per_share)
69
-
70
- asyncio.run(main())
71
- ```
72
-
73
- ## Resources
74
-
75
- | Resource | Methods |
76
- |---|---|
77
- | `client.transactions` | `.list(**params)`, `.paginate(**params)` |
78
- | `client.insiders` | `.search(name, **params)`, `.get(cik)`, `.transactions(cik, **params)` |
79
- | `client.companies` | `.get(ticker)`, `.insiders(ticker)` |
80
- | `client.signals` | `.list(**params)` — Business plan |
81
- | `client.webhooks` | `.create(url, event_types)`, `.list()`, `.delete(id)`, `.events(**params)` |
82
-
83
- ## Error handling
84
-
85
- ```python
86
- from form4api import Form4ApiClient, AuthError, PlanError, RateLimitError, NotFoundError
87
-
88
- client = Form4ApiClient("YOUR_API_KEY")
89
-
90
- try:
91
- signals = client.signals.list()
92
- except PlanError as e:
93
- print(f"Upgrade to {e.required_plan}")
94
- except RateLimitError as e:
95
- print(f"Retry after {e.retry_after}s")
96
- except AuthError:
97
- print("Invalid API key")
98
- except NotFoundError:
99
- print("Resource not found")
100
- ```
101
-
102
- ## License
103
-
104
- MIT
File without changes
File without changes
File without changes
File without changes
File without changes