the13f-mcp 0.1.1__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.
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.4
2
+ Name: the13f-mcp
3
+ Version: 0.1.1
4
+ Summary: Model Context Protocol server for the13f - institutional 13F intelligence in Claude Desktop, Cursor, and VS Code.
5
+ Author-email: Pickel Fintech <tom@pickelfintech.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://the13f.com/developers/
8
+ Project-URL: Documentation, https://the13f.com/developers/mcp.html
9
+ Project-URL: Signup, https://the13f.com/developers/
10
+ Project-URL: Source, https://gitlab.com/pickel-fintech/The13F
11
+ Project-URL: Changelog, https://gitlab.com/pickel-fintech/The13F/-/blob/main/mcp_server/CHANGELOG.md
12
+ Project-URL: Issues, https://gitlab.com/pickel-fintech/The13F/-/issues
13
+ Keywords: 13f,sec,institutional,investment,mcp,claude,cursor,anthropic
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Financial and Insurance Industry
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Topic :: Office/Business :: Financial :: Investment
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: mcp>=1.0
23
+ Requires-Dist: httpx>=0.27
24
+ Requires-Dist: pydantic>=2.6
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8.0; extra == "dev"
27
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
28
+ Requires-Dist: respx>=0.21; extra == "dev"
29
+ Requires-Dist: ruff>=0.4; extra == "dev"
30
+
31
+ # the13f-mcp
32
+
33
+ > Model Context Protocol server for [the13f](https://the13f.com). Brings
34
+ > institutional 13F intelligence into Claude Desktop, Cursor, VS Code +
35
+ > Continue, and any MCP-compatible host — no HTTP code required on your side.
36
+
37
+ ## Important limitations
38
+
39
+ Research data only. Sourced from publicly disclosed SEC Form 13F filings,
40
+ which lag quarter-end by up to 45 days. Past institutional positioning does
41
+ not predict future performance. Nothing here constitutes investment advice.
42
+ Every tool's response includes a `disclaimer` field; the host LLM should
43
+ surface it alongside any analysis it produces.
44
+
45
+ ## Install
46
+
47
+ ```
48
+ uvx the13f-mcp
49
+ ```
50
+
51
+ Or `pip install the13f-mcp` if you prefer to manage Python environments
52
+ yourself. Python 3.11+ required.
53
+
54
+ ## Get a free API key
55
+
56
+ Free, no payment, no credit card:
57
+
58
+ 1. Visit <https://the13f.com/developers/>
59
+ 2. Enter your email and solve the Cloudflare Turnstile challenge
60
+ 3. Copy your `pf13f_` key — **shown once**
61
+ 4. Paste it into your MCP host's config as `THE13F_API_KEY` (see below)
62
+ 5. Restart the host
63
+
64
+ Free tier: 100 read calls per day; quota rolls at UTC midnight. Signal and
65
+ report tools arrive in later minor versions when paid tiers launch.
66
+
67
+ ## Configure your MCP host
68
+
69
+ ### Claude Desktop
70
+
71
+ Edit `~/Library/Application Support/Claude/claude_desktop_config.json`
72
+ (macOS) or `%APPDATA%/Claude/claude_desktop_config.json` (Windows):
73
+
74
+ ```json
75
+ {
76
+ "mcpServers": {
77
+ "the13f": {
78
+ "command": "uvx",
79
+ "args": ["the13f-mcp"],
80
+ "env": {
81
+ "THE13F_API_KEY": "pf13f_..."
82
+ }
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ Restart Claude Desktop. The13f tools should appear in the MCP tool list.
89
+
90
+ ### Cursor
91
+
92
+ Settings → MCP → Add Server. Paste the same JSON block into the "Custom"
93
+ field.
94
+
95
+ ### VS Code with Continue
96
+
97
+ Settings → `experimental.modelContextProtocolServers` → add a `stdio`
98
+ transport running `uvx the13f-mcp` with `THE13F_API_KEY` in env. The key
99
+ reveal page at <https://the13f.com/developers/signup-success> shows a
100
+ copy-paste-ready snippet with the key already filled in.
101
+
102
+ ## Available tools — v0.1.0 (Read tier, free)
103
+
104
+ | Tool | What it does |
105
+ |------|---------------|
106
+ | `list_quarters` | All quarters with holdings data + the latest quarter |
107
+ | `search_managers` | Autocomplete 13F filers by name |
108
+ | `get_manager_holdings` | Full positions for a CIK + quarter |
109
+ | `get_manager_holdings_bulk` | Up to 25 `(cik, quarter)` pairs in one call |
110
+ | `list_all_managers` | Universe of 8,600+ filers with per-manager summary stats |
111
+ | `find_similar_managers` | Match a portfolio against the universe |
112
+ | `get_consensus_portfolio` | Most-widely-held securities per quarter |
113
+ | `get_market_regime` | Institutional regime snapshot (IIOI composite, state, transition) |
114
+ | `get_sector_flows` | Per-sector capital flows and risk posture |
115
+
116
+ ## Planned tools — later versions
117
+
118
+ | Version | Tools | Requires |
119
+ |---|---|---|
120
+ | v0.2.0 | `get_security_signals`, `get_security_signals_bulk`, `get_portfolio_signals` | Standard tier subscription |
121
+ | v0.3.0 | `generate_manager_report`, `generate_security_report`, `generate_sector_report`, `check_report_status`, `wait_and_download_report` | Standard tier + stored card; two-step confirm |
122
+
123
+ The free-tier Read tools are enough to explore the data and build prompts
124
+ against it. Signal and report tools deliberately wait on the paid-tier
125
+ billing plumbing.
126
+
127
+ ## Example prompts
128
+
129
+ > "Pull Berkshire Hathaway's top 20 positions as of Q4 2025 and summarize
130
+ > what grew the most quarter-over-quarter."
131
+
132
+ > "I own AAPL, MSFT, and NVDA equal-weight. Find the 5 institutional
133
+ > managers whose portfolios most closely resemble mine."
134
+
135
+ > "What sector did 13F filers most aggressively reduce last quarter? Show
136
+ > me the top three managers leading the reduction."
137
+
138
+ The MCP host's LLM picks the right tool, fills in arguments, surfaces the
139
+ `disclaimer`, and returns the result as structured JSON.
140
+
141
+ ## Environment variables
142
+
143
+ | Var | Default | What it controls |
144
+ |---|---|---|
145
+ | `THE13F_API_KEY` | (none) | Your `pf13f_` key. Without it, tools return a structured "free signup required" response with a link to the signup page. |
146
+ | `THE13F_API_BASE_URL` | `https://api.the13f.com` | Override for local development against a running copy of the13f's gui_server. |
147
+ | `THE13F_MCP_TIMEOUT` | `30` | Per-request HTTP timeout in seconds. Minimum 5. |
148
+
149
+ ## Source
150
+
151
+ Published from this directory of <https://gitlab.com/pickel-fintech/The13F>.
152
+ Tag pattern `sdk-mcp-vX.Y.Z` triggers a PyPI release via GitLab CI; see
153
+ `docs/mcp_launch.plan.md` in the repo root for the extraction story and the
154
+ release runbook.
155
+
156
+ ## Support
157
+
158
+ tom@pickelfintech.com — quote the `request_id` field returned by any failing
159
+ tool call for fastest triage.
160
+
161
+ ## License
162
+
163
+ MIT. See [LICENSE](../LICENSE). The MCP server source is MIT-licensed; use
164
+ of the the13f API itself is governed by the Terms of Service at
165
+ <https://the13f.com/terms.html>.
@@ -0,0 +1,135 @@
1
+ # the13f-mcp
2
+
3
+ > Model Context Protocol server for [the13f](https://the13f.com). Brings
4
+ > institutional 13F intelligence into Claude Desktop, Cursor, VS Code +
5
+ > Continue, and any MCP-compatible host — no HTTP code required on your side.
6
+
7
+ ## Important limitations
8
+
9
+ Research data only. Sourced from publicly disclosed SEC Form 13F filings,
10
+ which lag quarter-end by up to 45 days. Past institutional positioning does
11
+ not predict future performance. Nothing here constitutes investment advice.
12
+ Every tool's response includes a `disclaimer` field; the host LLM should
13
+ surface it alongside any analysis it produces.
14
+
15
+ ## Install
16
+
17
+ ```
18
+ uvx the13f-mcp
19
+ ```
20
+
21
+ Or `pip install the13f-mcp` if you prefer to manage Python environments
22
+ yourself. Python 3.11+ required.
23
+
24
+ ## Get a free API key
25
+
26
+ Free, no payment, no credit card:
27
+
28
+ 1. Visit <https://the13f.com/developers/>
29
+ 2. Enter your email and solve the Cloudflare Turnstile challenge
30
+ 3. Copy your `pf13f_` key — **shown once**
31
+ 4. Paste it into your MCP host's config as `THE13F_API_KEY` (see below)
32
+ 5. Restart the host
33
+
34
+ Free tier: 100 read calls per day; quota rolls at UTC midnight. Signal and
35
+ report tools arrive in later minor versions when paid tiers launch.
36
+
37
+ ## Configure your MCP host
38
+
39
+ ### Claude Desktop
40
+
41
+ Edit `~/Library/Application Support/Claude/claude_desktop_config.json`
42
+ (macOS) or `%APPDATA%/Claude/claude_desktop_config.json` (Windows):
43
+
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "the13f": {
48
+ "command": "uvx",
49
+ "args": ["the13f-mcp"],
50
+ "env": {
51
+ "THE13F_API_KEY": "pf13f_..."
52
+ }
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ Restart Claude Desktop. The13f tools should appear in the MCP tool list.
59
+
60
+ ### Cursor
61
+
62
+ Settings → MCP → Add Server. Paste the same JSON block into the "Custom"
63
+ field.
64
+
65
+ ### VS Code with Continue
66
+
67
+ Settings → `experimental.modelContextProtocolServers` → add a `stdio`
68
+ transport running `uvx the13f-mcp` with `THE13F_API_KEY` in env. The key
69
+ reveal page at <https://the13f.com/developers/signup-success> shows a
70
+ copy-paste-ready snippet with the key already filled in.
71
+
72
+ ## Available tools — v0.1.0 (Read tier, free)
73
+
74
+ | Tool | What it does |
75
+ |------|---------------|
76
+ | `list_quarters` | All quarters with holdings data + the latest quarter |
77
+ | `search_managers` | Autocomplete 13F filers by name |
78
+ | `get_manager_holdings` | Full positions for a CIK + quarter |
79
+ | `get_manager_holdings_bulk` | Up to 25 `(cik, quarter)` pairs in one call |
80
+ | `list_all_managers` | Universe of 8,600+ filers with per-manager summary stats |
81
+ | `find_similar_managers` | Match a portfolio against the universe |
82
+ | `get_consensus_portfolio` | Most-widely-held securities per quarter |
83
+ | `get_market_regime` | Institutional regime snapshot (IIOI composite, state, transition) |
84
+ | `get_sector_flows` | Per-sector capital flows and risk posture |
85
+
86
+ ## Planned tools — later versions
87
+
88
+ | Version | Tools | Requires |
89
+ |---|---|---|
90
+ | v0.2.0 | `get_security_signals`, `get_security_signals_bulk`, `get_portfolio_signals` | Standard tier subscription |
91
+ | v0.3.0 | `generate_manager_report`, `generate_security_report`, `generate_sector_report`, `check_report_status`, `wait_and_download_report` | Standard tier + stored card; two-step confirm |
92
+
93
+ The free-tier Read tools are enough to explore the data and build prompts
94
+ against it. Signal and report tools deliberately wait on the paid-tier
95
+ billing plumbing.
96
+
97
+ ## Example prompts
98
+
99
+ > "Pull Berkshire Hathaway's top 20 positions as of Q4 2025 and summarize
100
+ > what grew the most quarter-over-quarter."
101
+
102
+ > "I own AAPL, MSFT, and NVDA equal-weight. Find the 5 institutional
103
+ > managers whose portfolios most closely resemble mine."
104
+
105
+ > "What sector did 13F filers most aggressively reduce last quarter? Show
106
+ > me the top three managers leading the reduction."
107
+
108
+ The MCP host's LLM picks the right tool, fills in arguments, surfaces the
109
+ `disclaimer`, and returns the result as structured JSON.
110
+
111
+ ## Environment variables
112
+
113
+ | Var | Default | What it controls |
114
+ |---|---|---|
115
+ | `THE13F_API_KEY` | (none) | Your `pf13f_` key. Without it, tools return a structured "free signup required" response with a link to the signup page. |
116
+ | `THE13F_API_BASE_URL` | `https://api.the13f.com` | Override for local development against a running copy of the13f's gui_server. |
117
+ | `THE13F_MCP_TIMEOUT` | `30` | Per-request HTTP timeout in seconds. Minimum 5. |
118
+
119
+ ## Source
120
+
121
+ Published from this directory of <https://gitlab.com/pickel-fintech/The13F>.
122
+ Tag pattern `sdk-mcp-vX.Y.Z` triggers a PyPI release via GitLab CI; see
123
+ `docs/mcp_launch.plan.md` in the repo root for the extraction story and the
124
+ release runbook.
125
+
126
+ ## Support
127
+
128
+ tom@pickelfintech.com — quote the `request_id` field returned by any failing
129
+ tool call for fastest triage.
130
+
131
+ ## License
132
+
133
+ MIT. See [LICENSE](../LICENSE). The MCP server source is MIT-licensed; use
134
+ of the the13f API itself is governed by the Terms of Service at
135
+ <https://the13f.com/terms.html>.
@@ -0,0 +1,53 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "the13f-mcp"
7
+ version = "0.1.1"
8
+ description = "Model Context Protocol server for the13f - institutional 13F intelligence in Claude Desktop, Cursor, and VS Code."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Pickel Fintech", email = "tom@pickelfintech.com" }]
13
+ keywords = ["13f", "sec", "institutional", "investment", "mcp", "claude", "cursor", "anthropic"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Financial and Insurance Industry",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Topic :: Office/Business :: Financial :: Investment",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ ]
22
+
23
+ dependencies = [
24
+ "mcp>=1.0",
25
+ "httpx>=0.27",
26
+ "pydantic>=2.6",
27
+ ]
28
+
29
+ [project.optional-dependencies]
30
+ dev = [
31
+ "pytest>=8.0",
32
+ "pytest-asyncio>=0.23",
33
+ "respx>=0.21",
34
+ "ruff>=0.4",
35
+ ]
36
+
37
+ [project.scripts]
38
+ the13f-mcp = "the13f_mcp.server:main"
39
+
40
+ [project.urls]
41
+ Homepage = "https://the13f.com/developers/"
42
+ Documentation = "https://the13f.com/developers/mcp.html"
43
+ Signup = "https://the13f.com/developers/"
44
+ Source = "https://gitlab.com/pickel-fintech/The13F"
45
+ Changelog = "https://gitlab.com/pickel-fintech/The13F/-/blob/main/mcp_server/CHANGELOG.md"
46
+ Issues = "https://gitlab.com/pickel-fintech/The13F/-/issues"
47
+
48
+ [tool.setuptools.packages.find]
49
+ where = ["src"]
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]
53
+ asyncio_mode = "auto"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,24 @@
1
+ """the13f-mcp - Model Context Protocol server for the13f Platform.
2
+
3
+ Exposes institutional 13F intelligence as MCP tools to Claude Desktop, Cursor,
4
+ VS Code, Continue, and any MCP-compatible host. Read tools work for any signed-up
5
+ user (free tier); signal tools and PDF report generation require Standard+
6
+ subscription on platform.the13f.com.
7
+
8
+ Install:
9
+ uvx the13f-mcp
10
+
11
+ Configure in your MCP host (e.g. ~/Library/Application Support/Claude/claude_desktop_config.json):
12
+ {
13
+ "mcpServers": {
14
+ "the13f": {
15
+ "command": "uvx",
16
+ "args": ["the13f-mcp"],
17
+ "env": {"THE13F_API_KEY": "pf13f_..."}
18
+ }
19
+ }
20
+ }
21
+ """
22
+ from ._version import __version__
23
+
24
+ __all__ = ["__version__"]
@@ -0,0 +1,2 @@
1
+ """Version constant. Own module to avoid circular imports."""
2
+ __version__ = "0.1.1"
@@ -0,0 +1,39 @@
1
+ """Configuration loader for the MCP server.
2
+
3
+ Reads three environment variables:
4
+
5
+ THE13F_API_KEY - the user's pf13f_ key issued at
6
+ https://the13f.com/developers/signup. Optional:
7
+ read tools can surface a signup CTA on the first
8
+ call when the key is absent. THIS IS THE
9
+ CONVERSION GATE.
10
+
11
+ THE13F_API_BASE_URL - defaults to https://api.the13f.com. Override for
12
+ local development (e.g. http://127.0.0.1:5053).
13
+
14
+ THE13F_MCP_TIMEOUT - HTTP timeout in seconds. Default 30.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import os
19
+
20
+ DEFAULT_BASE_URL = "https://api.the13f.com"
21
+ DEFAULT_TIMEOUT_SECONDS = 30
22
+ SIGNUP_URL = "https://the13f.com/developers/"
23
+
24
+
25
+ def api_key() -> str | None:
26
+ raw = os.environ.get("THE13F_API_KEY", "").strip()
27
+ return raw or None
28
+
29
+
30
+ def base_url() -> str:
31
+ return (os.environ.get("THE13F_API_BASE_URL") or DEFAULT_BASE_URL).rstrip("/")
32
+
33
+
34
+ def timeout_seconds() -> float:
35
+ raw = os.environ.get("THE13F_MCP_TIMEOUT", str(DEFAULT_TIMEOUT_SECONDS))
36
+ try:
37
+ return max(5.0, float(raw))
38
+ except ValueError:
39
+ return float(DEFAULT_TIMEOUT_SECONDS)
@@ -0,0 +1,31 @@
1
+ """Canonical research-only disclaimer text for the MCP server.
2
+
3
+ Mirrors api_gateway/config/disclaimer.yml. Update both at the same time
4
+ (or - better - have the MCP fetch it dynamically from the API at startup;
5
+ that's a v0.2 enhancement).
6
+ """
7
+ CANONICAL_TEXT = (
8
+ "Research data only. Sourced from publicly disclosed SEC Form 13F "
9
+ "filings, which lag quarter-end by up to 45 days and reflect long-only "
10
+ "equity positions of the filing manager. Past institutional positioning "
11
+ "does not predict future performance and nothing here constitutes "
12
+ "investment advice, a recommendation, or a solicitation. Do your own "
13
+ "analysis before making investment decisions."
14
+ )
15
+
16
+ SHORT_FORM = "Research data only - not investment advice."
17
+
18
+ # Prefix for every tool description. The MCP host displays these to the LLM
19
+ # as part of the tool catalog; the LLM should re-surface our framing.
20
+ TOOL_DESCRIPTION_PREFIX = (
21
+ "Research data only. Returns disclosed institutional positions; "
22
+ "do not infer manager intent or future direction. "
23
+ )
24
+
25
+
26
+ def wrap(data: dict) -> dict:
27
+ """Add the disclaimer field to a tool result. Single helper for consistency."""
28
+ if not isinstance(data, dict):
29
+ data = {"data": data}
30
+ data.setdefault("disclaimer", CANONICAL_TEXT)
31
+ return data
@@ -0,0 +1,119 @@
1
+ """Shared HTTP client for the MCP server.
2
+
3
+ Wraps httpx with the platform's auth header, sane timeouts, retry-on-429
4
+ honoring Retry-After, and a small set of typed exceptions that the tool
5
+ modules surface back to the MCP host.
6
+
7
+ Importing this module is cheap; the httpx.Client is built lazily on first call.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import time
12
+ from typing import Any
13
+
14
+ import httpx
15
+
16
+ from . import config
17
+
18
+ _client: httpx.Client | None = None
19
+
20
+
21
+ class The13FApiError(RuntimeError):
22
+ """Base exception for any non-2xx response or transport error."""
23
+
24
+ def __init__(self, message: str, status_code: int | None = None, body: dict | None = None) -> None:
25
+ super().__init__(message)
26
+ self.status_code = status_code
27
+ self.body = body or {}
28
+ self.request_id = self.body.get("request_id")
29
+ self.docs_url = self.body.get("docs_url")
30
+ self.disclaimer = self.body.get("disclaimer")
31
+
32
+
33
+ class FreeSignupRequiredError(The13FApiError):
34
+ """Raised when no THE13F_API_KEY is set; surfaces signup CTA to the user."""
35
+
36
+
37
+ class TierUpgradeRequiredError(The13FApiError):
38
+ """403 - the user's tier doesn't unlock this feature."""
39
+
40
+
41
+ class PaymentRequiredError(The13FApiError):
42
+ """402 - quota exhausted + no credits OR card declined OR no card on file."""
43
+
44
+
45
+ def _client_lazy() -> httpx.Client:
46
+ global _client
47
+ if _client is None:
48
+ headers: dict[str, str] = {"User-Agent": _user_agent()}
49
+ api_key = config.api_key()
50
+ if api_key:
51
+ headers["X-API-Key"] = api_key
52
+ _client = httpx.Client(
53
+ base_url=config.base_url(),
54
+ timeout=config.timeout_seconds(),
55
+ headers=headers,
56
+ )
57
+ return _client
58
+
59
+
60
+ def _user_agent() -> str:
61
+ from ._version import __version__
62
+ return f"the13f-mcp/{__version__} (python-mcp; +{config.SIGNUP_URL})"
63
+
64
+
65
+ def request(method: str, path: str, **kwargs: Any) -> dict[str, Any]:
66
+ """Perform one API call with retry on 429.
67
+
68
+ Returns the parsed JSON dict. Raises one of the typed exceptions on
69
+ non-2xx responses.
70
+ """
71
+ if not config.api_key():
72
+ raise FreeSignupRequiredError(
73
+ f"Free signup required. Visit {config.SIGNUP_URL}, complete the "
74
+ "free email signup (Cloudflare Turnstile), copy the issued "
75
+ "pf13f_ key, and set THE13F_API_KEY in your MCP host config "
76
+ "(Claude Desktop, Cursor, or VS Code + Continue). Then restart "
77
+ "the host.",
78
+ status_code=401,
79
+ )
80
+
81
+ client = _client_lazy()
82
+ attempts = 0
83
+ while True:
84
+ attempts += 1
85
+ try:
86
+ response = client.request(method, path, **kwargs)
87
+ except httpx.TimeoutException as timeout_err:
88
+ raise The13FApiError(f"Timed out talking to the13f Platform: {timeout_err}") from timeout_err
89
+ except httpx.HTTPError as transport_err:
90
+ raise The13FApiError(f"Transport error: {transport_err}") from transport_err
91
+
92
+ if response.status_code == 429 and attempts < 3:
93
+ retry_after = response.headers.get("Retry-After", "1")
94
+ try:
95
+ wait_s = float(retry_after)
96
+ except ValueError:
97
+ wait_s = 1.0
98
+ time.sleep(min(wait_s, 30.0))
99
+ continue
100
+ return _interpret(response)
101
+
102
+
103
+ def _interpret(response: httpx.Response) -> dict[str, Any]:
104
+ """Parse the response. Raise typed exceptions for non-2xx."""
105
+ try:
106
+ body = response.json()
107
+ except ValueError:
108
+ body = {"error": response.text[:500]}
109
+ if 200 <= response.status_code < 300:
110
+ return body if isinstance(body, dict) else {"data": body}
111
+
112
+ message = body.get("error", f"HTTP {response.status_code}")
113
+ if response.status_code == 401:
114
+ raise FreeSignupRequiredError(message, status_code=401, body=body)
115
+ if response.status_code == 402:
116
+ raise PaymentRequiredError(message, status_code=402, body=body)
117
+ if response.status_code == 403:
118
+ raise TierUpgradeRequiredError(message, status_code=403, body=body)
119
+ raise The13FApiError(message, status_code=response.status_code, body=body)