agledger 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.
Files changed (42) hide show
  1. agledger-0.1.0/.gitignore +26 -0
  2. agledger-0.1.0/LICENSE +34 -0
  3. agledger-0.1.0/PKG-INFO +44 -0
  4. agledger-0.1.0/README.md +18 -0
  5. agledger-0.1.0/pyproject.toml +45 -0
  6. agledger-0.1.0/src/agledger/__init__.py +100 -0
  7. agledger-0.1.0/src/agledger/_client.py +179 -0
  8. agledger-0.1.0/src/agledger/_errors.py +153 -0
  9. agledger-0.1.0/src/agledger/_http.py +442 -0
  10. agledger-0.1.0/src/agledger/py.typed +0 -0
  11. agledger-0.1.0/src/agledger/resources/__init__.py +39 -0
  12. agledger-0.1.0/src/agledger/resources/a2a.py +30 -0
  13. agledger-0.1.0/src/agledger/resources/admin.py +425 -0
  14. agledger-0.1.0/src/agledger/resources/capabilities.py +31 -0
  15. agledger-0.1.0/src/agledger/resources/compliance.py +182 -0
  16. agledger-0.1.0/src/agledger/resources/dashboard.py +66 -0
  17. agledger-0.1.0/src/agledger/resources/disputes.py +48 -0
  18. agledger-0.1.0/src/agledger/resources/enterprises.py +76 -0
  19. agledger-0.1.0/src/agledger/resources/events.py +48 -0
  20. agledger-0.1.0/src/agledger/resources/health.py +21 -0
  21. agledger-0.1.0/src/agledger/resources/mandates.py +385 -0
  22. agledger-0.1.0/src/agledger/resources/notarize.py +57 -0
  23. agledger-0.1.0/src/agledger/resources/projects.py +91 -0
  24. agledger-0.1.0/src/agledger/resources/proxy.py +219 -0
  25. agledger-0.1.0/src/agledger/resources/receipts.py +69 -0
  26. agledger-0.1.0/src/agledger/resources/registration.py +54 -0
  27. agledger-0.1.0/src/agledger/resources/reputation.py +40 -0
  28. agledger-0.1.0/src/agledger/resources/schemas.py +262 -0
  29. agledger-0.1.0/src/agledger/resources/verification.py +30 -0
  30. agledger-0.1.0/src/agledger/resources/webhooks.py +186 -0
  31. agledger-0.1.0/src/agledger/scopes.py +140 -0
  32. agledger-0.1.0/src/agledger/types.py +498 -0
  33. agledger-0.1.0/src/agledger/webhooks/__init__.py +7 -0
  34. agledger-0.1.0/src/agledger/webhooks/verify.py +95 -0
  35. agledger-0.1.0/tests/__init__.py +0 -0
  36. agledger-0.1.0/tests/test_client.py +351 -0
  37. agledger-0.1.0/tests/test_errors.py +115 -0
  38. agledger-0.1.0/tests/test_pagination.py +51 -0
  39. agledger-0.1.0/tests/test_retry.py +115 -0
  40. agledger-0.1.0/tests/test_schemas.py +237 -0
  41. agledger-0.1.0/tests/test_stream.py +144 -0
  42. agledger-0.1.0/tests/test_webhooks.py +58 -0
@@ -0,0 +1,26 @@
1
+ node_modules/
2
+ dist/
3
+ .turbo/
4
+ *.tsbuildinfo
5
+ coverage/
6
+ .DS_Store
7
+ *.log
8
+ .env
9
+ .env.*
10
+ *.db
11
+ *.sqlite
12
+ *.tgz
13
+ .vscode/
14
+ .idea/
15
+ .claude/
16
+ CLAUDE.md
17
+ agledger-sdk/
18
+
19
+ # Python
20
+ __pycache__/
21
+ *.pyc
22
+ *.pyo
23
+ *.egg-info/
24
+ .venv/
25
+ .pytest_cache/
26
+ dist/
agledger-0.1.0/LICENSE ADDED
@@ -0,0 +1,34 @@
1
+ Copyright (c) 2026 AGLedger LLC. All rights reserved.
2
+
3
+ PROPRIETARY SOFTWARE LICENSE
4
+
5
+ This software and associated documentation files (the "Software") are the
6
+ proprietary property of AGLedger LLC. The Software is provided for use
7
+ exclusively with the AGLedger API (https://api.agledger.ai).
8
+
9
+ Permission is hereby granted to use the Software solely for the purpose of
10
+ integrating with AGLedger services via the AGLedger API, subject to the
11
+ following conditions:
12
+
13
+ 1. You may not copy, modify, merge, publish, distribute, sublicense, or sell
14
+ copies of the Software, except as required to use it with the AGLedger API.
15
+
16
+ 2. You may not use the Software to create a competing product or service.
17
+
18
+ 3. You may not reverse engineer, decompile, or disassemble the Software,
19
+ except to the extent permitted by applicable law.
20
+
21
+ Use of this SDK is subject to the AGLedger Terms of Service at
22
+ https://agledger.ai/terms.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
27
+ AGLEDGER LLC BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
28
+ AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
29
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
+
31
+ ---
32
+
33
+ AGLedger™ and the Agentic Contract Specification™ are trademarks of
34
+ AGLedger LLC. Patent pending.
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: agledger
3
+ Version: 0.1.0
4
+ Summary: AGLedger SDK — Accountability and audit infrastructure for agentic systems.
5
+ Author-email: AGLedger LLC <support@agledger.ai>
6
+ License-Expression: LicenseRef-Proprietary
7
+ License-File: LICENSE
8
+ Keywords: accountability,agents,agledger,audit,mandates
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.9
19
+ Requires-Dist: httpx<1,>=0.25.0
20
+ Requires-Dist: pydantic<3,>=2.0.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
23
+ Requires-Dist: pytest>=8.0; extra == 'dev'
24
+ Requires-Dist: respx>=0.22; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # AGLedger Python SDK
28
+
29
+ Accountability and audit infrastructure for agentic systems.
30
+
31
+ ```python
32
+ from agledger import AgledgerClient
33
+
34
+ client = AgledgerClient(api_key="ach_ent_...")
35
+ mandate = client.mandates.create(
36
+ enterpriseId="ent_123",
37
+ contractType="ACH-PROC-v1",
38
+ contractVersion="1",
39
+ platform="internal",
40
+ criteria={"item": "widgets", "maxQuantity": 100},
41
+ )
42
+ ```
43
+
44
+ See [agledger.ai](https://agledger.ai) for documentation.
@@ -0,0 +1,18 @@
1
+ # AGLedger Python SDK
2
+
3
+ Accountability and audit infrastructure for agentic systems.
4
+
5
+ ```python
6
+ from agledger import AgledgerClient
7
+
8
+ client = AgledgerClient(api_key="ach_ent_...")
9
+ mandate = client.mandates.create(
10
+ enterpriseId="ent_123",
11
+ contractType="ACH-PROC-v1",
12
+ contractVersion="1",
13
+ platform="internal",
14
+ criteria={"item": "widgets", "maxQuantity": 100},
15
+ )
16
+ ```
17
+
18
+ See [agledger.ai](https://agledger.ai) for documentation.
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "agledger"
7
+ version = "0.1.0"
8
+ description = "AGLedger SDK — Accountability and audit infrastructure for agentic systems."
9
+ readme = "README.md"
10
+ license = "LicenseRef-Proprietary"
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "AGLedger LLC", email = "support@agledger.ai" }]
13
+ keywords = ["agledger", "mandates", "accountability", "agents", "audit"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Typing :: Typed",
24
+ ]
25
+ dependencies = [
26
+ "httpx>=0.25.0,<1",
27
+ "pydantic>=2.0.0,<3",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ dev = [
32
+ "pytest>=8.0",
33
+ "pytest-asyncio>=0.24",
34
+ "respx>=0.22",
35
+ ]
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/agledger"]
39
+
40
+ [tool.pytest.ini_options]
41
+ asyncio_mode = "auto"
42
+ testpaths = ["tests"]
43
+
44
+ [tool.pyright]
45
+ typeCheckingMode = "strict"
@@ -0,0 +1,100 @@
1
+ """
2
+ AGLedger SDK — Accountability and audit infrastructure for agentic systems.
3
+
4
+ Patent Pending. Copyright 2026 AGLedger LLC. All rights reserved.
5
+ """
6
+
7
+ from agledger._client import AgledgerClient, AsyncAgledgerClient
8
+ from agledger._errors import (
9
+ AgledgerError,
10
+ APIConnectionError,
11
+ APIError,
12
+ APITimeoutError,
13
+ AuthenticationError,
14
+ BadRequestError,
15
+ NotFoundError,
16
+ PermissionDeniedError,
17
+ RateLimitError,
18
+ UnprocessableError,
19
+ )
20
+ from agledger.scopes import (
21
+ SCOPE_PROFILES,
22
+ ScopeProfile,
23
+ ScopeProfileName,
24
+ Scopes,
25
+ )
26
+ from agledger.types import (
27
+ AccountProfile,
28
+ AuditChain,
29
+ AuditStreamResult,
30
+ ComplianceRecord,
31
+ ContractType,
32
+ DashboardSummary,
33
+ Dispute,
34
+ DisputeStatus,
35
+ EnterpriseAgentRecord,
36
+ Event,
37
+ HealthResponse,
38
+ Mandate,
39
+ MandateStatus,
40
+ MandateStatusSummary,
41
+ MandateTransitionAction,
42
+ OperatingMode,
43
+ OutcomeResult,
44
+ Page,
45
+ Receipt,
46
+ RegisterResult,
47
+ ReputationScore,
48
+ VerificationMode,
49
+ VerificationResult,
50
+ Webhook,
51
+ WebhookEventType,
52
+ )
53
+
54
+ __version__ = "0.1.0"
55
+ __all__ = [
56
+ "AgledgerClient",
57
+ "AsyncAgledgerClient",
58
+ # Errors
59
+ "AgledgerError",
60
+ "APIError",
61
+ "AuthenticationError",
62
+ "PermissionDeniedError",
63
+ "NotFoundError",
64
+ "BadRequestError",
65
+ "UnprocessableError",
66
+ "RateLimitError",
67
+ "APIConnectionError",
68
+ "APITimeoutError",
69
+ # Scopes
70
+ "Scopes",
71
+ "ScopeProfile",
72
+ "SCOPE_PROFILES",
73
+ "ScopeProfileName",
74
+ # Types
75
+ "Mandate",
76
+ "MandateStatus",
77
+ "MandateTransitionAction",
78
+ "Receipt",
79
+ "ContractType",
80
+ "OperatingMode",
81
+ "VerificationMode",
82
+ "VerificationResult",
83
+ "Dispute",
84
+ "DisputeStatus",
85
+ "Webhook",
86
+ "WebhookEventType",
87
+ "ReputationScore",
88
+ "Event",
89
+ "AuditChain",
90
+ "DashboardSummary",
91
+ "AccountProfile",
92
+ "RegisterResult",
93
+ "EnterpriseAgentRecord",
94
+ "HealthResponse",
95
+ "Page",
96
+ "OutcomeResult",
97
+ "MandateStatusSummary",
98
+ "ComplianceRecord",
99
+ "AuditStreamResult",
100
+ ]
@@ -0,0 +1,179 @@
1
+ """
2
+ AGLedger SDK — Client with 18 resource sub-clients.
3
+ Supports context manager protocol for proper connection cleanup.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from types import TracebackType
9
+ from typing import Optional
10
+
11
+ import httpx
12
+
13
+ from agledger._http import (
14
+ AsyncHttpClient,
15
+ HttpClient,
16
+ DEFAULT_BASE_URL,
17
+ DEFAULT_MAX_RETRIES,
18
+ DEFAULT_TIMEOUT,
19
+ _resolve_api_key,
20
+ )
21
+ from agledger.resources.a2a import A2AResource, AsyncA2AResource
22
+ from agledger.resources.admin import AdminResource, AsyncAdminResource
23
+ from agledger.resources.capabilities import AsyncCapabilitiesResource, CapabilitiesResource
24
+ from agledger.resources.compliance import AsyncComplianceResource, ComplianceResource
25
+ from agledger.resources.dashboard import AsyncDashboardResource, DashboardResource
26
+ from agledger.resources.disputes import AsyncDisputesResource, DisputesResource
27
+ from agledger.resources.enterprises import AsyncEnterprisesResource, EnterprisesResource
28
+ from agledger.resources.events import AsyncEventsResource, EventsResource
29
+ from agledger.resources.health import AsyncHealthResource, HealthResource
30
+ from agledger.resources.mandates import AsyncMandatesResource, MandatesResource
31
+ from agledger.resources.notarize import AsyncNotarizeResource, NotarizeResource
32
+ from agledger.resources.projects import AsyncProjectsResource, ProjectsResource
33
+ from agledger.resources.proxy import AsyncProxyResource, ProxyResource
34
+ from agledger.resources.receipts import AsyncReceiptsResource, ReceiptsResource
35
+ from agledger.resources.registration import AsyncRegistrationResource, RegistrationResource
36
+ from agledger.resources.reputation import AsyncReputationResource, ReputationResource
37
+ from agledger.resources.schemas import AsyncSchemasResource, SchemasResource
38
+ from agledger.resources.verification import AsyncVerificationResource, VerificationResource
39
+ from agledger.resources.webhooks import AsyncWebhooksResource, WebhooksResource
40
+
41
+
42
+ class AgledgerClient:
43
+ """
44
+ Synchronous AGLedger API client with 18 resource sub-clients.
45
+
46
+ Supports context manager for automatic connection cleanup::
47
+
48
+ with AgledgerClient(api_key="ach_ent_...") as client:
49
+ mandate = client.mandates.create(
50
+ enterprise_id="ent_123",
51
+ contract_type="ACH-PROC-v1",
52
+ contract_version="1",
53
+ platform="internal",
54
+ criteria={"item": "widgets"},
55
+ )
56
+
57
+ Or pass ``AGLEDGER_API_KEY`` env var::
58
+
59
+ client = AgledgerClient() # reads from env
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ api_key: str | None = None,
65
+ *,
66
+ base_url: str = DEFAULT_BASE_URL,
67
+ max_retries: int = DEFAULT_MAX_RETRIES,
68
+ timeout: float = DEFAULT_TIMEOUT,
69
+ idempotency_key_prefix: str = "",
70
+ http_client: httpx.Client | None = None,
71
+ ) -> None:
72
+ resolved_key = _resolve_api_key(api_key)
73
+ self._http = HttpClient(
74
+ api_key=resolved_key,
75
+ base_url=base_url,
76
+ max_retries=max_retries,
77
+ timeout=timeout,
78
+ idempotency_key_prefix=idempotency_key_prefix,
79
+ http_client=http_client,
80
+ )
81
+ self.mandates = MandatesResource(self._http)
82
+ self.receipts = ReceiptsResource(self._http)
83
+ self.verification = VerificationResource(self._http)
84
+ self.disputes = DisputesResource(self._http)
85
+ self.webhooks = WebhooksResource(self._http)
86
+ self.reputation = ReputationResource(self._http)
87
+ self.events = EventsResource(self._http)
88
+ self.schemas = SchemasResource(self._http)
89
+ self.dashboard = DashboardResource(self._http)
90
+ self.compliance = ComplianceResource(self._http)
91
+ self.registration = RegistrationResource(self._http)
92
+ self.health = HealthResource(self._http)
93
+ self.admin = AdminResource(self._http)
94
+ self.a2a = A2AResource(self._http)
95
+ self.capabilities = CapabilitiesResource(self._http)
96
+ self.notarize = NotarizeResource(self._http)
97
+ self.enterprises = EnterprisesResource(self._http)
98
+ self.proxy = ProxyResource(self._http)
99
+ self.projects = ProjectsResource(self._http)
100
+
101
+ @property
102
+ def last_request_id(self) -> str | None:
103
+ """Request ID from the most recent API response (``X-Request-Id`` header)."""
104
+ return self._http.last_request_id
105
+
106
+ def close(self) -> None:
107
+ """Close the underlying HTTP connection pool."""
108
+ self._http.close()
109
+
110
+ def __enter__(self) -> AgledgerClient:
111
+ return self
112
+
113
+ def __exit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None) -> None:
114
+ self.close()
115
+
116
+
117
+ class AsyncAgledgerClient:
118
+ """
119
+ Async AGLedger API client with 18 resource sub-clients.
120
+
121
+ Supports async context manager::
122
+
123
+ async with AsyncAgledgerClient(api_key="ach_ent_...") as client:
124
+ mandate = await client.mandates.get("mnd-123")
125
+ """
126
+
127
+ def __init__(
128
+ self,
129
+ api_key: str | None = None,
130
+ *,
131
+ base_url: str = DEFAULT_BASE_URL,
132
+ max_retries: int = DEFAULT_MAX_RETRIES,
133
+ timeout: float = DEFAULT_TIMEOUT,
134
+ idempotency_key_prefix: str = "",
135
+ http_client: httpx.AsyncClient | None = None,
136
+ ) -> None:
137
+ resolved_key = _resolve_api_key(api_key)
138
+ self._http = AsyncHttpClient(
139
+ api_key=resolved_key,
140
+ base_url=base_url,
141
+ max_retries=max_retries,
142
+ timeout=timeout,
143
+ idempotency_key_prefix=idempotency_key_prefix,
144
+ http_client=http_client,
145
+ )
146
+ self.mandates = AsyncMandatesResource(self._http)
147
+ self.receipts = AsyncReceiptsResource(self._http)
148
+ self.verification = AsyncVerificationResource(self._http)
149
+ self.disputes = AsyncDisputesResource(self._http)
150
+ self.webhooks = AsyncWebhooksResource(self._http)
151
+ self.reputation = AsyncReputationResource(self._http)
152
+ self.events = AsyncEventsResource(self._http)
153
+ self.schemas = AsyncSchemasResource(self._http)
154
+ self.dashboard = AsyncDashboardResource(self._http)
155
+ self.compliance = AsyncComplianceResource(self._http)
156
+ self.registration = AsyncRegistrationResource(self._http)
157
+ self.health = AsyncHealthResource(self._http)
158
+ self.admin = AsyncAdminResource(self._http)
159
+ self.a2a = AsyncA2AResource(self._http)
160
+ self.capabilities = AsyncCapabilitiesResource(self._http)
161
+ self.notarize = AsyncNotarizeResource(self._http)
162
+ self.enterprises = AsyncEnterprisesResource(self._http)
163
+ self.proxy = AsyncProxyResource(self._http)
164
+ self.projects = AsyncProjectsResource(self._http)
165
+
166
+ @property
167
+ def last_request_id(self) -> str | None:
168
+ """Request ID from the most recent API response (``X-Request-Id`` header)."""
169
+ return self._http.last_request_id
170
+
171
+ async def close(self) -> None:
172
+ """Close the underlying HTTP connection pool."""
173
+ await self._http.close()
174
+
175
+ async def __aenter__(self) -> AsyncAgledgerClient:
176
+ return self
177
+
178
+ async def __aexit__(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None) -> None:
179
+ await self.close()
@@ -0,0 +1,153 @@
1
+ """
2
+ AGLedger SDK — Error classes.
3
+ Follows Anthropic/OpenAI naming conventions. Never shadows Python builtins.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any, Optional
9
+
10
+
11
+ class AgledgerError(Exception):
12
+ """Base error for all SDK errors."""
13
+
14
+ pass
15
+
16
+
17
+ class APIError(AgledgerError):
18
+ """API returned an error response.
19
+
20
+ Key properties for agent consumers:
21
+
22
+ - ``status`` — HTTP status code
23
+ - ``code`` — stable machine-readable error code
24
+ - ``suggestion`` — agent-friendly recovery hint
25
+ - ``retryable`` — whether this error can be retried
26
+ - ``doc_url`` — link to error documentation
27
+ """
28
+
29
+ status: int
30
+ code: str
31
+ request_id: Optional[str]
32
+ details: Optional[Any]
33
+ retryable: bool
34
+ doc_url: str
35
+ suggestion: Optional[str]
36
+
37
+ __slots__ = ("status", "code", "request_id", "details", "retryable", "doc_url", "suggestion")
38
+
39
+ _DEFAULT_SUGGESTIONS: dict[int, str] = {
40
+ 401: "Check your API key. Set AGLEDGER_API_KEY or pass api_key to the client constructor.",
41
+ 403: "Check API key scopes. See error.missing_scopes for required scopes, or use a broader scope profile.",
42
+ 404: "The resource was not found. Verify the ID is correct, or list resources to find valid IDs.",
43
+ 409: "A conflicting operation is in progress. Wait and retry, or check the current resource state.",
44
+ 422: "The resource is in the wrong state for this operation. Check the current status before retrying.",
45
+ 429: "Rate limit exceeded. The SDK retries automatically — if you see this, increase max_retries or add backoff.",
46
+ }
47
+
48
+ def __init__(
49
+ self,
50
+ status: int,
51
+ *,
52
+ message: str = "",
53
+ code: str = "unknown",
54
+ request_id: Optional[str] = None,
55
+ details: Optional[Any] = None,
56
+ retryable: Optional[bool] = None,
57
+ suggestion: Optional[str] = None,
58
+ ) -> None:
59
+ self.status = status
60
+ self.code = code
61
+ self.request_id = request_id
62
+ self.details = details
63
+ self.retryable = retryable if retryable is not None else (status == 429 or status >= 500)
64
+ self.doc_url = f"https://docs.agledger.ai/errors/{self.code}"
65
+ self.suggestion = suggestion or self._DEFAULT_SUGGESTIONS.get(status)
66
+ super().__init__(message or f"API error {status}")
67
+
68
+ def __repr__(self) -> str:
69
+ return f"{self.__class__.__name__}(status={self.status}, code={self.code!r}, message={str(self)!r})"
70
+
71
+ def is_retryable(self) -> bool:
72
+ """Whether this error can be retried (429, 5xx, network errors)."""
73
+ return self.retryable
74
+
75
+ def is_input_error(self) -> bool:
76
+ """Whether this is a 400 validation error — fix the request and retry."""
77
+ return self.status == 400
78
+
79
+ def is_state_error(self) -> bool:
80
+ """Whether this is a 422 state error — resource is in the wrong state."""
81
+ return self.status == 422
82
+
83
+ def is_auth_error(self) -> bool:
84
+ """Whether this is an auth error (401/403)."""
85
+ return self.status in (401, 403)
86
+
87
+
88
+ class AuthenticationError(APIError):
89
+ """401 — invalid or missing API key."""
90
+
91
+ def __init__(self, **kwargs: Any) -> None:
92
+ super().__init__(401, **kwargs)
93
+
94
+
95
+ class PermissionDeniedError(APIError):
96
+ """403 — insufficient scopes or permissions."""
97
+
98
+ missing_scopes: list[str]
99
+ key_scopes: Optional[list[str]]
100
+
101
+ def __init__(
102
+ self,
103
+ *,
104
+ missing_scopes: Optional[list[str]] = None,
105
+ key_scopes: Optional[list[str]] = None,
106
+ **kwargs: Any,
107
+ ) -> None:
108
+ self.missing_scopes = missing_scopes or []
109
+ self.key_scopes = key_scopes
110
+ super().__init__(403, **kwargs)
111
+
112
+
113
+ class NotFoundError(APIError):
114
+ """404 — resource not found."""
115
+
116
+ def __init__(self, **kwargs: Any) -> None:
117
+ super().__init__(404, **kwargs)
118
+
119
+
120
+ class BadRequestError(APIError):
121
+ """400 — request validation failed."""
122
+
123
+ def __init__(self, **kwargs: Any) -> None:
124
+ super().__init__(400, **kwargs)
125
+
126
+
127
+ class UnprocessableError(APIError):
128
+ """422 — resource in wrong state for the requested operation."""
129
+
130
+ def __init__(self, **kwargs: Any) -> None:
131
+ super().__init__(422, **kwargs)
132
+
133
+
134
+ class RateLimitError(APIError):
135
+ """429 — rate limit exceeded."""
136
+
137
+ retry_after: Optional[float]
138
+
139
+ def __init__(self, *, retry_after: Optional[float] = None, **kwargs: Any) -> None:
140
+ self.retry_after = retry_after
141
+ super().__init__(429, **kwargs)
142
+
143
+
144
+ class APIConnectionError(AgledgerError):
145
+ """Network connectivity error."""
146
+
147
+ pass
148
+
149
+
150
+ class APITimeoutError(APIConnectionError):
151
+ """Request timed out."""
152
+
153
+ pass