arkleon 0.1.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.
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ venv/
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .coverage
12
+ htmlcov/
arkleon-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arkleon LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
arkleon-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,206 @@
1
+ Metadata-Version: 2.5
2
+ Name: arkleon
3
+ Version: 0.1.0
4
+ Summary: Point-in-time SEC EDGAR fundamentals: free fetch/parse helpers, an optional certified /v1 client, and a built-in MCP server.
5
+ Project-URL: Homepage, https://github.com/arkleon/arkleon
6
+ Project-URL: Repository, https://github.com/arkleon/arkleon
7
+ Project-URL: SEC fair access, https://www.sec.gov/os/accessing-edgar-data
8
+ Author-email: Arkleon LLC <founder@arkleon.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: 10-K,10-Q,as-reported,edgar,filings,financial-data,financial-statement-data-sets,fundamentals,mcp,point-in-time,sec,xbrl
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Office/Business :: Financial
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: httpx>=0.27
21
+ Provides-Extra: all
22
+ Requires-Dist: httpx>=0.27; extra == 'all'
23
+ Requires-Dist: mcp>=1.9; extra == 'all'
24
+ Requires-Dist: pandas>=2.0; extra == 'all'
25
+ Provides-Extra: api
26
+ Requires-Dist: httpx>=0.27; extra == 'api'
27
+ Provides-Extra: mcp
28
+ Requires-Dist: mcp>=1.9; extra == 'mcp'
29
+ Provides-Extra: pandas
30
+ Requires-Dist: pandas>=2.0; extra == 'pandas'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # arkleon
34
+
35
+ Point-in-time SEC EDGAR fundamentals: free EDGAR fetch/parse helpers with zero
36
+ credentials, plus an optional certified `/v1` client and a built-in MCP server.
37
+
38
+ The free EDGAR core requires no account and no API key. The optional paid `/v1`
39
+ client and the corpus-backed point-in-time features are the only parts that need
40
+ a key.
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ pip install arkleon
46
+ ```
47
+
48
+ ## Quickstart: free, no API key
49
+
50
+ The EDGAR helpers work on the first line of code with no Arkleon account and no
51
+ key. The only string they require is an SEC fair-access `User-Agent`, which you
52
+ write yourself.
53
+
54
+ ```python
55
+ from arkleon.edgar import EdgarClient
56
+
57
+ # SEC fair-access User-Agent: a self-declared contact string, NOT an Arkleon key.
58
+ edgar = EdgarClient(user_agent="Acme Research contact@acme.com")
59
+
60
+ # Fetch a company's 10-K filings by CIK (Apple = 320193):
61
+ for filing in edgar.filings(cik=320193, form="10-K"):
62
+ print(filing.filed, filing.accession, filing.form)
63
+
64
+ # Parse as-reported numeric facts, filtered best-effort to a date:
65
+ assets = edgar.concept(cik=320193, tag="Assets", taxonomy="us-gaap")
66
+ for fact in assets.as_of("2020-01-01"): # keep only filed <= 2020-01-01
67
+ print(fact.period_end, fact.value, fact.unit, fact.filed)
68
+ ```
69
+
70
+ *No account, no key, no Arkleon network. Live-EDGAR, best-effort point-in-time.*
71
+
72
+ ## Quickstart: optional paid `/v1` client (certified point-in-time)
73
+
74
+ ```python
75
+ # pip install "arkleon[api]"
76
+ from arkleon.api import DataClient
77
+
78
+ client = DataClient(api_key="ak_...") # or ARKLEON_API_KEY env
79
+ page = client.facts(cik=320193, tag="Assets", as_of="2020-01-01")
80
+ for fact in page.data:
81
+ print(fact["period_end"], fact["value"], fact.get("source_url"))
82
+ ```
83
+
84
+ *Certified corpus. `as_of` required, filters on filing date, identical query +
85
+ `as_of` returns identical data permanently.*
86
+
87
+ ## The free/paid boundary
88
+
89
+ The `arkleon.edgar` helpers never require a key and never contact Arkleon: their
90
+ only remote hosts are `data.sec.gov` and `www.sec.gov`. Only the `arkleon[api]`
91
+ client and the corpus-backed point-in-time features call Arkleon, and they refuse
92
+ to operate without an `ak_`-prefixed key. Installing `arkleon[api]` does not by
93
+ itself activate the paid path; a key must still be supplied at runtime.
94
+
95
+ ## MCP server
96
+
97
+ Install the server extra and launch the stdio server with the console script:
98
+
99
+ ```bash
100
+ pip install "arkleon[mcp]"
101
+ arkleon-mcp
102
+ ```
103
+
104
+ An agent host launches it the same way. Set `ARKLEON_API_KEY` in the launch
105
+ environment to unlock the paid tools:
106
+
107
+ ```json
108
+ {
109
+ "mcpServers": {
110
+ "arkleon": {
111
+ "command": "arkleon-mcp",
112
+ "env": { "ARKLEON_API_KEY": "ak_..." }
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ The free EDGAR tools register **unconditionally**; the paid tools register **only
119
+ when an `ak_` key is present**. An agent with no key sees exactly the free set.
120
+
121
+ | Tool | Backed by | Key required? |
122
+ |---|---|---|
123
+ | `edgar_list_filings` | free EDGAR core | No |
124
+ | `edgar_company_facts` | free EDGAR core | No |
125
+ | `edgar_concept` | free EDGAR core | No |
126
+ | `edgar_facts_as_of` (best-effort, live EDGAR) | free EDGAR core | No |
127
+ | `edgar_resolve_cik` (current-snapshot, non-point-in-time) | free EDGAR core | No |
128
+ | `pit_facts` (certified point-in-time) | paid `/v1` client | Yes |
129
+ | `pit_filings` | paid `/v1` client | Yes |
130
+ | `resolve_company` | paid `/v1` client | Yes |
131
+ | `pit_revision_history` | paid `/v1` client | Yes |
132
+
133
+ ## What it returns, and what it does not
134
+
135
+ **Returns:** as-reported numeric XBRL facts, filing and company metadata, and the
136
+ lineage linking each figure to its source filing.
137
+
138
+ **Does not return:** market prices, analyst estimates, narrative disclosure
139
+ (MD&A, risk factors, footnotes), or derived analytics, ratios, scores, or
140
+ signals. Neither layer serves these. It is a research corpus, not a real-time
141
+ filing feed: the paid corpus refreshes on the SEC's quarterly cadence.
142
+
143
+ ## Point-in-time, in two sentences
144
+
145
+ `as_of` filters on the **filing date** (`filed <= as_of`), never on the period a
146
+ fact describes, so a query sees only values that were already public on that
147
+ date. Because a fact from a later filing never appears, a historical query cannot
148
+ observe information that did not yet exist, which is what keeps a study free of
149
+ look-ahead bias.
150
+
151
+ ## The certified corpus (paid)
152
+
153
+ The paid `/v1` client serves the certified corpus. Per
154
+ `docs/data-layer/api-v1-contract.md` Appendix A, which is the sole authority for
155
+ what any surface may state, the claimable figures are:
156
+
157
+ | Quantity | Figure | Kind |
158
+ |---|---|---|
159
+ | Filings | 426,003 | EXACT |
160
+ | Facts | 181,350,662 | EXACT |
161
+ | Distinct companies (CIK) | 16,811 | EXACT |
162
+ | Quarters spanned | 69 (2009q1–2026q1) | exact |
163
+ | Quarters populated | 68 | exact |
164
+
165
+ The EXACT/exact label travels with each number and is not presentational. These
166
+ figures are keyed to the date each filing became public, include values that
167
+ later filings revised, and trace to their source filing. The earliest filed date
168
+ in the corpus is 2009-04-15, so a query with an `as_of` earlier than that date
169
+ correctly returns empty. Treat the contract's Appendix A, not this table, as the
170
+ authority: a later corpus revision updates the contract, and any figure that
171
+ disagrees with it is stale.
172
+
173
+ ## Identifiers
174
+
175
+ **CIK is the addressable identifier** across both layers. In the free layer,
176
+ `resolve_cik(ticker)` maps a symbol to a CIK from SEC's current ticker snapshot;
177
+ it is a convenience, not a reproducible identity resolver, because it has no
178
+ ticker history, a symbol is reassigned over time, and one CIK carries several
179
+ symbols at once (`GOOG` and `GOOGL`, `BRK.A` and `BRK.B`). The `/v1` API does not
180
+ serve the `ticker` filter today: a `ticker` request returns a distinct
181
+ "not served in this release" error rather than silently resolving through the
182
+ free snapshot. Address facts by CIK.
183
+
184
+ ## Install matrix
185
+
186
+ | Install | Adds |
187
+ |---|---|
188
+ | `arkleon` | The free EDGAR fetch/parse core. No key, no Arkleon network. |
189
+ | `arkleon[api]` | The optional paid `/v1` `DataClient`. Requires an `ak_` key at runtime. |
190
+ | `arkleon[mcp]` | The built-in MCP server and the `arkleon-mcp` console script. |
191
+ | `arkleon[pandas]` | Optional pandas DataFrame adapters for the free core. Never required. |
192
+ | `arkleon[all]` | Convenience union of `api`, `mcp`, and `pandas`. |
193
+
194
+ Base plus `[pandas]` stays fully free and credential-free. `[api]` and the paid
195
+ tools in `[mcp]` are the only parts that consume an `ak_` key.
196
+
197
+ ## License
198
+
199
+ MIT. See [LICENSE](LICENSE).
200
+
201
+ ## Links
202
+
203
+ - **`/v1` API contract:** `docs/data-layer/api-v1-contract.md` (Appendix A is the
204
+ authority for the corpus figures above).
205
+ - **Issues and contributing:** https://github.com/arkleon/arkleon
206
+ - **SEC fair-access policy:** https://www.sec.gov/os/accessing-edgar-data
@@ -0,0 +1,174 @@
1
+ # arkleon
2
+
3
+ Point-in-time SEC EDGAR fundamentals: free EDGAR fetch/parse helpers with zero
4
+ credentials, plus an optional certified `/v1` client and a built-in MCP server.
5
+
6
+ The free EDGAR core requires no account and no API key. The optional paid `/v1`
7
+ client and the corpus-backed point-in-time features are the only parts that need
8
+ a key.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pip install arkleon
14
+ ```
15
+
16
+ ## Quickstart: free, no API key
17
+
18
+ The EDGAR helpers work on the first line of code with no Arkleon account and no
19
+ key. The only string they require is an SEC fair-access `User-Agent`, which you
20
+ write yourself.
21
+
22
+ ```python
23
+ from arkleon.edgar import EdgarClient
24
+
25
+ # SEC fair-access User-Agent: a self-declared contact string, NOT an Arkleon key.
26
+ edgar = EdgarClient(user_agent="Acme Research contact@acme.com")
27
+
28
+ # Fetch a company's 10-K filings by CIK (Apple = 320193):
29
+ for filing in edgar.filings(cik=320193, form="10-K"):
30
+ print(filing.filed, filing.accession, filing.form)
31
+
32
+ # Parse as-reported numeric facts, filtered best-effort to a date:
33
+ assets = edgar.concept(cik=320193, tag="Assets", taxonomy="us-gaap")
34
+ for fact in assets.as_of("2020-01-01"): # keep only filed <= 2020-01-01
35
+ print(fact.period_end, fact.value, fact.unit, fact.filed)
36
+ ```
37
+
38
+ *No account, no key, no Arkleon network. Live-EDGAR, best-effort point-in-time.*
39
+
40
+ ## Quickstart: optional paid `/v1` client (certified point-in-time)
41
+
42
+ ```python
43
+ # pip install "arkleon[api]"
44
+ from arkleon.api import DataClient
45
+
46
+ client = DataClient(api_key="ak_...") # or ARKLEON_API_KEY env
47
+ page = client.facts(cik=320193, tag="Assets", as_of="2020-01-01")
48
+ for fact in page.data:
49
+ print(fact["period_end"], fact["value"], fact.get("source_url"))
50
+ ```
51
+
52
+ *Certified corpus. `as_of` required, filters on filing date, identical query +
53
+ `as_of` returns identical data permanently.*
54
+
55
+ ## The free/paid boundary
56
+
57
+ The `arkleon.edgar` helpers never require a key and never contact Arkleon: their
58
+ only remote hosts are `data.sec.gov` and `www.sec.gov`. Only the `arkleon[api]`
59
+ client and the corpus-backed point-in-time features call Arkleon, and they refuse
60
+ to operate without an `ak_`-prefixed key. Installing `arkleon[api]` does not by
61
+ itself activate the paid path; a key must still be supplied at runtime.
62
+
63
+ ## MCP server
64
+
65
+ Install the server extra and launch the stdio server with the console script:
66
+
67
+ ```bash
68
+ pip install "arkleon[mcp]"
69
+ arkleon-mcp
70
+ ```
71
+
72
+ An agent host launches it the same way. Set `ARKLEON_API_KEY` in the launch
73
+ environment to unlock the paid tools:
74
+
75
+ ```json
76
+ {
77
+ "mcpServers": {
78
+ "arkleon": {
79
+ "command": "arkleon-mcp",
80
+ "env": { "ARKLEON_API_KEY": "ak_..." }
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ The free EDGAR tools register **unconditionally**; the paid tools register **only
87
+ when an `ak_` key is present**. An agent with no key sees exactly the free set.
88
+
89
+ | Tool | Backed by | Key required? |
90
+ |---|---|---|
91
+ | `edgar_list_filings` | free EDGAR core | No |
92
+ | `edgar_company_facts` | free EDGAR core | No |
93
+ | `edgar_concept` | free EDGAR core | No |
94
+ | `edgar_facts_as_of` (best-effort, live EDGAR) | free EDGAR core | No |
95
+ | `edgar_resolve_cik` (current-snapshot, non-point-in-time) | free EDGAR core | No |
96
+ | `pit_facts` (certified point-in-time) | paid `/v1` client | Yes |
97
+ | `pit_filings` | paid `/v1` client | Yes |
98
+ | `resolve_company` | paid `/v1` client | Yes |
99
+ | `pit_revision_history` | paid `/v1` client | Yes |
100
+
101
+ ## What it returns, and what it does not
102
+
103
+ **Returns:** as-reported numeric XBRL facts, filing and company metadata, and the
104
+ lineage linking each figure to its source filing.
105
+
106
+ **Does not return:** market prices, analyst estimates, narrative disclosure
107
+ (MD&A, risk factors, footnotes), or derived analytics, ratios, scores, or
108
+ signals. Neither layer serves these. It is a research corpus, not a real-time
109
+ filing feed: the paid corpus refreshes on the SEC's quarterly cadence.
110
+
111
+ ## Point-in-time, in two sentences
112
+
113
+ `as_of` filters on the **filing date** (`filed <= as_of`), never on the period a
114
+ fact describes, so a query sees only values that were already public on that
115
+ date. Because a fact from a later filing never appears, a historical query cannot
116
+ observe information that did not yet exist, which is what keeps a study free of
117
+ look-ahead bias.
118
+
119
+ ## The certified corpus (paid)
120
+
121
+ The paid `/v1` client serves the certified corpus. Per
122
+ `docs/data-layer/api-v1-contract.md` Appendix A, which is the sole authority for
123
+ what any surface may state, the claimable figures are:
124
+
125
+ | Quantity | Figure | Kind |
126
+ |---|---|---|
127
+ | Filings | 426,003 | EXACT |
128
+ | Facts | 181,350,662 | EXACT |
129
+ | Distinct companies (CIK) | 16,811 | EXACT |
130
+ | Quarters spanned | 69 (2009q1–2026q1) | exact |
131
+ | Quarters populated | 68 | exact |
132
+
133
+ The EXACT/exact label travels with each number and is not presentational. These
134
+ figures are keyed to the date each filing became public, include values that
135
+ later filings revised, and trace to their source filing. The earliest filed date
136
+ in the corpus is 2009-04-15, so a query with an `as_of` earlier than that date
137
+ correctly returns empty. Treat the contract's Appendix A, not this table, as the
138
+ authority: a later corpus revision updates the contract, and any figure that
139
+ disagrees with it is stale.
140
+
141
+ ## Identifiers
142
+
143
+ **CIK is the addressable identifier** across both layers. In the free layer,
144
+ `resolve_cik(ticker)` maps a symbol to a CIK from SEC's current ticker snapshot;
145
+ it is a convenience, not a reproducible identity resolver, because it has no
146
+ ticker history, a symbol is reassigned over time, and one CIK carries several
147
+ symbols at once (`GOOG` and `GOOGL`, `BRK.A` and `BRK.B`). The `/v1` API does not
148
+ serve the `ticker` filter today: a `ticker` request returns a distinct
149
+ "not served in this release" error rather than silently resolving through the
150
+ free snapshot. Address facts by CIK.
151
+
152
+ ## Install matrix
153
+
154
+ | Install | Adds |
155
+ |---|---|
156
+ | `arkleon` | The free EDGAR fetch/parse core. No key, no Arkleon network. |
157
+ | `arkleon[api]` | The optional paid `/v1` `DataClient`. Requires an `ak_` key at runtime. |
158
+ | `arkleon[mcp]` | The built-in MCP server and the `arkleon-mcp` console script. |
159
+ | `arkleon[pandas]` | Optional pandas DataFrame adapters for the free core. Never required. |
160
+ | `arkleon[all]` | Convenience union of `api`, `mcp`, and `pandas`. |
161
+
162
+ Base plus `[pandas]` stays fully free and credential-free. `[api]` and the paid
163
+ tools in `[mcp]` are the only parts that consume an `ak_` key.
164
+
165
+ ## License
166
+
167
+ MIT. See [LICENSE](LICENSE).
168
+
169
+ ## Links
170
+
171
+ - **`/v1` API contract:** `docs/data-layer/api-v1-contract.md` (Appendix A is the
172
+ authority for the corpus figures above).
173
+ - **Issues and contributing:** https://github.com/arkleon/arkleon
174
+ - **SEC fair-access policy:** https://www.sec.gov/os/accessing-edgar-data
@@ -0,0 +1,60 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "arkleon"
7
+ version = "0.1.0"
8
+ description = "Point-in-time SEC EDGAR fundamentals: free fetch/parse helpers, an optional certified /v1 client, and a built-in MCP server."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ authors = [{ name = "Arkleon LLC", email = "founder@arkleon.com" }]
13
+ keywords = [
14
+ "sec",
15
+ "edgar",
16
+ "xbrl",
17
+ "fundamentals",
18
+ "point-in-time",
19
+ "as-reported",
20
+ "financial-data",
21
+ "filings",
22
+ "10-K",
23
+ "10-Q",
24
+ "mcp",
25
+ "financial-statement-data-sets",
26
+ ]
27
+ dependencies = ["httpx>=0.27"]
28
+ classifiers = [
29
+ "License :: OSI Approved :: MIT License",
30
+ "Programming Language :: Python :: 3.11",
31
+ "Programming Language :: Python :: 3.12",
32
+ "Programming Language :: Python :: 3.13",
33
+ "Development Status :: 3 - Alpha",
34
+ "Intended Audience :: Developers",
35
+ "Topic :: Office/Business :: Financial",
36
+ ]
37
+
38
+ [project.optional-dependencies]
39
+ api = ["httpx>=0.27"]
40
+ mcp = ["mcp>=1.9"]
41
+ pandas = ["pandas>=2.0"]
42
+ all = ["httpx>=0.27", "mcp>=1.9", "pandas>=2.0"]
43
+
44
+ [project.urls]
45
+ Homepage = "https://github.com/arkleon/arkleon"
46
+ Repository = "https://github.com/arkleon/arkleon"
47
+ "SEC fair access" = "https://www.sec.gov/os/accessing-edgar-data"
48
+
49
+ [project.scripts]
50
+ arkleon-mcp = "arkleon.mcp.server:main"
51
+
52
+ [tool.hatch.build.targets.wheel]
53
+ packages = ["src/arkleon"]
54
+
55
+ [tool.hatch.build.targets.sdist]
56
+ include = [
57
+ "/src/arkleon",
58
+ "/README.md",
59
+ "/LICENSE",
60
+ ]
@@ -0,0 +1,48 @@
1
+ """Free EDGAR core imported by default; api and mcp are optional extras."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.1.0"
6
+
7
+ from .edgar import (
8
+ AS_OF_WARNING,
9
+ Company,
10
+ EdgarClient,
11
+ Fact,
12
+ FactSet,
13
+ Filing,
14
+ MissingUserAgentError,
15
+ RateLimiter,
16
+ as_of,
17
+ build_ticker_map,
18
+ cik_to_int,
19
+ facts_to_dataframe,
20
+ normalize_cik,
21
+ parse_company_concept,
22
+ parse_company_facts,
23
+ parse_fsds_zip,
24
+ parse_submissions,
25
+ resolve_user_agent,
26
+ )
27
+
28
+ __all__ = [
29
+ "AS_OF_WARNING",
30
+ "Company",
31
+ "EdgarClient",
32
+ "Fact",
33
+ "FactSet",
34
+ "Filing",
35
+ "MissingUserAgentError",
36
+ "RateLimiter",
37
+ "as_of",
38
+ "build_ticker_map",
39
+ "cik_to_int",
40
+ "facts_to_dataframe",
41
+ "normalize_cik",
42
+ "parse_company_concept",
43
+ "parse_company_facts",
44
+ "parse_fsds_zip",
45
+ "parse_submissions",
46
+ "resolve_user_agent",
47
+ "__version__",
48
+ ]
@@ -0,0 +1,49 @@
1
+ """Optional paid /v1 client for the Arkleon Data API.
2
+
3
+ Component (b) of the arkleon package (spec section 6). This subpackage is
4
+ always importable because its HTTP dependency (httpx) is a base dependency;
5
+ there is no import gate. The paid gate is a RUNTIME requirement instead: the
6
+ client refuses to construct without an ak_-prefixed key (contract section 6.2),
7
+ resolved from the constructor argument or the ARKLEON_API_KEY environment
8
+ variable.
9
+
10
+ Nothing here activates without a key. Installing the client does not activate
11
+ the paid path; a key must still be supplied at runtime.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from .client import DataClient
17
+ from .errors import (
18
+ ArkleonAPIError,
19
+ AuthError,
20
+ ConflictError,
21
+ NotFoundError,
22
+ NotServedError,
23
+ RateLimitError,
24
+ RequestError,
25
+ ScopeError,
26
+ ServerError,
27
+ UnprocessableError,
28
+ error_from_envelope,
29
+ )
30
+ from .errors import NotImplementedError # noqa: A004 (contract alias, spec section 6.5)
31
+ from .pagination import Page
32
+
33
+ __all__ = [
34
+ "DataClient",
35
+ "Page",
36
+ "error_from_envelope",
37
+ "ArkleonAPIError",
38
+ "AuthError",
39
+ "ScopeError",
40
+ "RequestError",
41
+ "NotFoundError",
42
+ "ConflictError",
43
+ "UnprocessableError",
44
+ "RateLimitError",
45
+ "NotServedError",
46
+ "ServerError",
47
+ # Alias exported per spec section 6.5; primary name is NotServedError.
48
+ "NotImplementedError",
49
+ ]
@@ -0,0 +1,58 @@
1
+ """The paid gate: ak_ Bearer key resolution.
2
+
3
+ The key is read from an explicit argument or, failing that, the
4
+ ``ARKLEON_API_KEY`` environment variable (contract section 6.2). The client
5
+ refuses to operate without an ak_-prefixed key, raising locally before any
6
+ network contact so an unauthenticated request is never sent. Keys are
7
+ presented, never stored or logged: nothing here writes the key to a file, a
8
+ log line, or an exception message.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+
15
+ from .errors import AuthError
16
+
17
+ __all__ = ["resolve_api_key", "optional_api_key", "API_KEY_ENV_VAR", "API_KEY_PREFIX"]
18
+
19
+ API_KEY_ENV_VAR = "ARKLEON_API_KEY"
20
+ API_KEY_PREFIX = "ak_"
21
+
22
+
23
+ def resolve_api_key(explicit: str | None) -> str:
24
+ """Return an ak_-prefixed key or raise AuthError locally.
25
+
26
+ Resolution order: the explicit argument, then ``ARKLEON_API_KEY``. The key
27
+ is required and validated for the ak_ prefix here, before any request, so
28
+ the client fails closed rather than issuing a silent unauthenticated call
29
+ (contract section 6.2). The raised message never contains the key value.
30
+ """
31
+ key = explicit if explicit is not None else os.environ.get(API_KEY_ENV_VAR)
32
+ if not isinstance(key, str) or not key.strip():
33
+ raise AuthError(
34
+ "An Arkleon /v1 API key is required. Pass api_key=... or set the "
35
+ f"{API_KEY_ENV_VAR} environment variable to an {API_KEY_PREFIX}-prefixed key.",
36
+ error="unauthorized",
37
+ )
38
+ key = key.strip()
39
+ if not key.startswith(API_KEY_PREFIX):
40
+ raise AuthError(
41
+ f"Arkleon /v1 API keys must be {API_KEY_PREFIX}-prefixed. The supplied "
42
+ "value is not a valid key.",
43
+ error="unauthorized",
44
+ )
45
+ return key
46
+
47
+
48
+ def optional_api_key(explicit: str | None) -> str | None:
49
+ """Return a resolvable ak_ key, or None when none is available.
50
+
51
+ A soft companion to resolve_api_key for callers that must not raise when no
52
+ key is present, such as the MCP server deciding whether to register the paid
53
+ tools (spec section 7.1). It never raises and never logs the key.
54
+ """
55
+ try:
56
+ return resolve_api_key(explicit)
57
+ except AuthError:
58
+ return None