agentscore-py 2.4.2__tar.gz → 2.5.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.
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/.github/workflows/ci.yml +2 -2
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/.github/workflows/publish.yml +3 -3
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/.github/workflows/security.yml +1 -1
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/CODE_OF_CONDUCT.md +1 -1
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/PKG-INFO +21 -21
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/README.md +19 -19
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/SECURITY.md +1 -1
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/agentscore/__init__.py +4 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/agentscore/client.py +26 -5
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/agentscore/errors.py +1 -1
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/agentscore/types.py +42 -5
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/pyproject.toml +2 -2
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/tests/test_client.py +148 -14
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/tests/test_types.py +5 -5
- agentscore_py-2.5.0/uv.lock +452 -0
- agentscore_py-2.4.2/uv.lock +0 -467
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/.claude/CLAUDE.md +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/.github/dependabot.yml +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/.gitignore +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/CONTRIBUTING.md +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/LICENSE +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/agentscore/py.typed +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/agentscore/test_mode.py +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/lefthook.yml +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/ruff.toml +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/tests/__init__.py +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/tests/conftest.py +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/tests/test_errors.py +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/tests/test_integration.py +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/tests/test_test_mode.py +0 -0
- {agentscore_py-2.4.2 → agentscore_py-2.5.0}/vulture_whitelist.py +0 -0
|
@@ -19,9 +19,9 @@ jobs:
|
|
|
19
19
|
timeout-minutes: 10
|
|
20
20
|
steps:
|
|
21
21
|
- uses: useblacksmith/checkout@v1
|
|
22
|
-
- uses: astral-sh/setup-uv@v8.
|
|
22
|
+
- uses: astral-sh/setup-uv@v8.3.0
|
|
23
23
|
- run: uv python install 3.12
|
|
24
|
-
- uses: actions/cache@
|
|
24
|
+
- uses: actions/cache@v6
|
|
25
25
|
with:
|
|
26
26
|
path: |
|
|
27
27
|
~/.cache/uv
|
|
@@ -17,9 +17,9 @@ jobs:
|
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
18
|
timeout-minutes: 15
|
|
19
19
|
steps:
|
|
20
|
-
- uses: actions/checkout@
|
|
20
|
+
- uses: actions/checkout@v7
|
|
21
21
|
|
|
22
|
-
- uses: astral-sh/setup-uv@v8.
|
|
22
|
+
- uses: astral-sh/setup-uv@v8.3.0
|
|
23
23
|
|
|
24
24
|
- name: Set version from tag
|
|
25
25
|
run: sed -i "s/^version = .*/version = \"${GITHUB_REF_NAME#v}\"/" pyproject.toml
|
|
@@ -27,7 +27,7 @@ jobs:
|
|
|
27
27
|
- run: uv build
|
|
28
28
|
|
|
29
29
|
- name: Publish to PyPI
|
|
30
|
-
uses: pypa/gh-action-pypi-publish@
|
|
30
|
+
uses: pypa/gh-action-pypi-publish@v1.14.0
|
|
31
31
|
|
|
32
32
|
- name: Create GitHub Release
|
|
33
33
|
run: gh release create "$GITHUB_REF_NAME" --generate-notes
|
|
@@ -24,7 +24,7 @@ Examples of unacceptable behavior:
|
|
|
24
24
|
|
|
25
25
|
## Enforcement
|
|
26
26
|
|
|
27
|
-
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the project team at engineering@agentscore.
|
|
27
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the project team at engineering@agentscore.com. All complaints will be reviewed and investigated promptly and fairly.
|
|
28
28
|
|
|
29
29
|
## Attribution
|
|
30
30
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentscore-py
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: Python client for the AgentScore APIs
|
|
5
|
-
Project-URL: Homepage, https://agentscore.
|
|
5
|
+
Project-URL: Homepage, https://www.agentscore.com
|
|
6
6
|
Project-URL: Repository, https://github.com/agentscore/python-sdk
|
|
7
7
|
Project-URL: Issues, https://github.com/agentscore/python-sdk/issues
|
|
8
8
|
License-Expression: MIT
|
|
@@ -26,7 +26,7 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
[](https://pypi.org/project/agentscore-py/)
|
|
27
27
|
[](LICENSE)
|
|
28
28
|
|
|
29
|
-
Python client for
|
|
29
|
+
The official Python client for [AgentScore](https://www.agentscore.com). Identity verification and compliance gating for agentic commerce (KYC, age, sanctions, jurisdiction), sync and async.
|
|
30
30
|
|
|
31
31
|
## Install
|
|
32
32
|
|
|
@@ -84,7 +84,7 @@ print(session["next_steps"]["action"]) # "deliver_verify_url_and_poll"
|
|
|
84
84
|
|
|
85
85
|
status = client.poll_session(session["session_id"], session["poll_secret"])
|
|
86
86
|
if status["status"] == "verified":
|
|
87
|
-
print(status["operator_token"]) # "opc_..."
|
|
87
|
+
print(status["operator_token"]) # "opc_...", use for future requests
|
|
88
88
|
|
|
89
89
|
# Optional pre-association: attach the session to a known wallet or refresh KYC
|
|
90
90
|
# for an existing operator credential.
|
|
@@ -94,7 +94,7 @@ client.create_session(operator_token="opc_...") # KYC refresh
|
|
|
94
94
|
|
|
95
95
|
### Wallet resolution
|
|
96
96
|
|
|
97
|
-
`assess()` responses include `resolved_operator` and `linked_wallets
|
|
97
|
+
`assess()` responses include `resolved_operator` and `linked_wallets`, all same-operator sibling wallets (claimed via SIWE or captured via prior `associate_wallet`). The list may mix EVM addresses (`0x...` lowercased) and Solana addresses (base58, case-preserved) for cross-chain operators; merchants doing wallet-signer-match checks should accept a payment signed by any address in the list, regardless of chain. The `address` parameter on `assess()` and `get_reputation()` accepts either format; the network is auto-detected from the address shape.
|
|
98
98
|
|
|
99
99
|
### Server-side signer-match + sanctions screening
|
|
100
100
|
|
|
@@ -124,9 +124,9 @@ if sanctions and sanctions.get("sanctioned"):
|
|
|
124
124
|
print("OFAC hit:", sanctions["ofac_label"], sanctions["sdn_uid"])
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
Wallet-OFAC SDN enforcement on the `signer` block is unconditional whenever a signer is supplied
|
|
127
|
+
Wallet-OFAC SDN enforcement on the `signer` block is unconditional whenever a signer is supplied; no `policy.require_sanctions_clear` opt-in is required. The API flips `decision` to `deny` when `signer_sanctions` is `sanctioned: True` OR `status: "unavailable"`, with `decision_reasons` including `sanctions_flagged` or `sanctions_check_unavailable` respectively (fail-closed; OFAC strict-liability). `policy.require_sanctions_clear` is the separate NAME-based screen on the resolved operator's KYC identity.
|
|
128
128
|
|
|
129
|
-
Pass `signer["address"] = None` for rails without a wallet signer (Stripe SPT, card-only). The API responds with `signer_match["kind"] == "wallet_auth_requires_wallet_signing"` and a parsed `agent_instructions` block telling the agent to switch to `X-Operator-Token` auth
|
|
129
|
+
Pass `signer["address"] = None` for rails without a wallet signer (Stripe SPT, card-only). The API responds with `signer_match["kind"] == "wallet_auth_requires_wallet_signing"` and a parsed `agent_instructions` block telling the agent to switch to `X-Operator-Token` auth, spread the block directly into a 403 body.
|
|
130
130
|
|
|
131
131
|
### Credential Management
|
|
132
132
|
|
|
@@ -140,14 +140,14 @@ client.revoke_credential(cred["id"])
|
|
|
140
140
|
|
|
141
141
|
### Report an Agent's Wallet (Cross-Merchant Attribution)
|
|
142
142
|
|
|
143
|
-
After an agent authenticated via `operator_token` completes a payment, report the signer wallet so AgentScore can build a cross-merchant credential↔wallet profile. Fire-and-forget
|
|
143
|
+
After an agent authenticated via `operator_token` completes a payment, report the signer wallet so AgentScore can build a cross-merchant credential↔wallet profile. Fire-and-forget, `first_seen` is informational only. `network` is the key-derivation family: `"evm"` for any EVM chain (Base, Tempo, Ethereum, …) or `"solana"` for Solana.
|
|
144
144
|
|
|
145
145
|
```python
|
|
146
146
|
client.associate_wallet(
|
|
147
147
|
operator_token="opc_...",
|
|
148
148
|
wallet_address=signer_from_payment, # e.g. EIP-3009 `from` or Tempo MPP DID address
|
|
149
149
|
network="evm",
|
|
150
|
-
idempotency_key=payment_intent_id, # optional
|
|
150
|
+
idempotency_key=payment_intent_id, # optional, agent retries of the same payment no-op
|
|
151
151
|
)
|
|
152
152
|
```
|
|
153
153
|
|
|
@@ -184,8 +184,8 @@ with AgentScore(api_key="as_live_...") as client:
|
|
|
184
184
|
|
|
185
185
|
| Parameter | Default | Description |
|
|
186
186
|
|---------------|-----------------------------|--------------------------|
|
|
187
|
-
| `api_key` |
|
|
188
|
-
| `base_url` | `https://api.agentscore.
|
|
187
|
+
| `api_key` | _(required)_ | API key from [agentscore.com](https://www.agentscore.com); raises `ValueError` if missing |
|
|
188
|
+
| `base_url` | `https://api.agentscore.com` | API base URL |
|
|
189
189
|
| `timeout` | `10.0` | Request timeout (seconds)|
|
|
190
190
|
| `user_agent` | `None` | Prepended to the default `User-Agent` as `"{user_agent} (agentscore-py/{version})"`. Use to attribute API calls to your app. |
|
|
191
191
|
|
|
@@ -202,7 +202,7 @@ except AgentScoreError as e:
|
|
|
202
202
|
print(e.code, e.status_code, str(e))
|
|
203
203
|
```
|
|
204
204
|
|
|
205
|
-
`AgentScoreError.details` carries the rest of the response body
|
|
205
|
+
`AgentScoreError.details` carries the rest of the response body (`verify_url`, `linked_wallets`, `claimed_operator`, `actual_signer`, `expected_signer`, `reasons`, `agent_memory`) so callers can branch on granular denial codes without re-parsing.
|
|
206
206
|
|
|
207
207
|
### Typed error classes
|
|
208
208
|
|
|
@@ -211,11 +211,11 @@ For status-code-specific recovery, the SDK raises typed subclasses of `AgentScor
|
|
|
211
211
|
| Class | Triggered by | What it adds |
|
|
212
212
|
|---|---|---|
|
|
213
213
|
| `PaymentRequiredError` | HTTP 402 | The endpoint is not enabled for this account |
|
|
214
|
-
| `TokenExpiredError` | HTTP 401 with `error.code = "token_expired"` | Parsed body fields on the instance: `verify_url`, `session_id`, `poll_secret`, `poll_url`, `next_steps`, `agent_memory
|
|
215
|
-
| `InvalidCredentialError` | HTTP 401 with `error.code = "invalid_credential"` | Permanent
|
|
214
|
+
| `TokenExpiredError` | HTTP 401 with `error.code = "token_expired"` | Parsed body fields on the instance: `verify_url`, `session_id`, `poll_secret`, `poll_url`, `next_steps`, `agent_memory`, recover without re-parsing `details` |
|
|
215
|
+
| `InvalidCredentialError` | HTTP 401 with `error.code = "invalid_credential"` | Permanent, switch tokens or restart |
|
|
216
216
|
| `QuotaExceededError` | HTTP 429 with `error.code = "quota_exceeded"` | Account-level cap reached; don't retry |
|
|
217
217
|
| `RateLimitedError` | HTTP 429 with `error.code = "rate_limited"` | Per-second sliding-window cap; retry after `Retry-After` |
|
|
218
|
-
| `TimeoutError` | `httpx.TimeoutException` (connect/read/write/pool timeout) | Distinct from generic network errors. Note: subclasses `AgentScoreError`, **not** the builtin `TimeoutError
|
|
218
|
+
| `TimeoutError` | `httpx.TimeoutException` (connect/read/write/pool timeout) | Distinct from generic network errors. Note: subclasses `AgentScoreError`, **not** the builtin `TimeoutError`, import explicitly from `agentscore.errors` to disambiguate. |
|
|
219
219
|
|
|
220
220
|
All non-timeout `httpx.HTTPError` (ConnectError, ProtocolError, NetworkError, etc.) are wrapped as `AgentScoreError(code="network_error", status_code=0)`.
|
|
221
221
|
|
|
@@ -230,11 +230,11 @@ try:
|
|
|
230
230
|
except TokenExpiredError as e:
|
|
231
231
|
print("Verify at:", e.verify_url, "poll with:", e.poll_secret)
|
|
232
232
|
except QuotaExceededError as e:
|
|
233
|
-
print("Account quota reached
|
|
233
|
+
print("Account quota reached, surface to user; don't retry.")
|
|
234
234
|
except AgentScoreTimeoutError:
|
|
235
|
-
print("Network timeout
|
|
235
|
+
print("Network timeout, retry with backoff.")
|
|
236
236
|
except AgentScoreError as e:
|
|
237
|
-
print(e.code, e
|
|
237
|
+
print(e.code, str(e))
|
|
238
238
|
```
|
|
239
239
|
|
|
240
240
|
## Quota observability
|
|
@@ -247,10 +247,10 @@ quota = result.get("quota")
|
|
|
247
247
|
if quota and quota["limit"] and quota["used"]:
|
|
248
248
|
pct = (quota["used"] / quota["limit"]) * 100
|
|
249
249
|
if pct > 80:
|
|
250
|
-
print(f"AgentScore quota at {pct:.1f}
|
|
250
|
+
print(f"AgentScore quota at {pct:.1f}%, resets {quota['reset']}")
|
|
251
251
|
```
|
|
252
252
|
|
|
253
|
-
`quota` is absent when the API doesn't emit the headers (Enterprise / unlimited tiers). On a 429 response the SDK raises `QuotaExceededError` / `RateLimitedError` instead of returning a body, so `quota` is only readable on successful calls
|
|
253
|
+
`quota` is absent when the API doesn't emit the headers (Enterprise / unlimited tiers). On a 429 response the SDK raises `QuotaExceededError` / `RateLimitedError` instead of returning a body, so `quota` is only readable on successful calls; drive proactive alerting off the success-path field.
|
|
254
254
|
|
|
255
255
|
## Telemetry
|
|
256
256
|
|
|
@@ -258,7 +258,7 @@ if quota and quota["limit"] and quota["used"]:
|
|
|
258
258
|
|
|
259
259
|
## Documentation
|
|
260
260
|
|
|
261
|
-
- [API Reference](https://docs.agentscore.
|
|
261
|
+
- [API Reference](https://docs.agentscore.com)
|
|
262
262
|
|
|
263
263
|
## License
|
|
264
264
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://pypi.org/project/agentscore-py/)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
|
|
6
|
-
Python client for
|
|
6
|
+
The official Python client for [AgentScore](https://www.agentscore.com). Identity verification and compliance gating for agentic commerce (KYC, age, sanctions, jurisdiction), sync and async.
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
@@ -61,7 +61,7 @@ print(session["next_steps"]["action"]) # "deliver_verify_url_and_poll"
|
|
|
61
61
|
|
|
62
62
|
status = client.poll_session(session["session_id"], session["poll_secret"])
|
|
63
63
|
if status["status"] == "verified":
|
|
64
|
-
print(status["operator_token"]) # "opc_..."
|
|
64
|
+
print(status["operator_token"]) # "opc_...", use for future requests
|
|
65
65
|
|
|
66
66
|
# Optional pre-association: attach the session to a known wallet or refresh KYC
|
|
67
67
|
# for an existing operator credential.
|
|
@@ -71,7 +71,7 @@ client.create_session(operator_token="opc_...") # KYC refresh
|
|
|
71
71
|
|
|
72
72
|
### Wallet resolution
|
|
73
73
|
|
|
74
|
-
`assess()` responses include `resolved_operator` and `linked_wallets
|
|
74
|
+
`assess()` responses include `resolved_operator` and `linked_wallets`, all same-operator sibling wallets (claimed via SIWE or captured via prior `associate_wallet`). The list may mix EVM addresses (`0x...` lowercased) and Solana addresses (base58, case-preserved) for cross-chain operators; merchants doing wallet-signer-match checks should accept a payment signed by any address in the list, regardless of chain. The `address` parameter on `assess()` and `get_reputation()` accepts either format; the network is auto-detected from the address shape.
|
|
75
75
|
|
|
76
76
|
### Server-side signer-match + sanctions screening
|
|
77
77
|
|
|
@@ -101,9 +101,9 @@ if sanctions and sanctions.get("sanctioned"):
|
|
|
101
101
|
print("OFAC hit:", sanctions["ofac_label"], sanctions["sdn_uid"])
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
Wallet-OFAC SDN enforcement on the `signer` block is unconditional whenever a signer is supplied
|
|
104
|
+
Wallet-OFAC SDN enforcement on the `signer` block is unconditional whenever a signer is supplied; no `policy.require_sanctions_clear` opt-in is required. The API flips `decision` to `deny` when `signer_sanctions` is `sanctioned: True` OR `status: "unavailable"`, with `decision_reasons` including `sanctions_flagged` or `sanctions_check_unavailable` respectively (fail-closed; OFAC strict-liability). `policy.require_sanctions_clear` is the separate NAME-based screen on the resolved operator's KYC identity.
|
|
105
105
|
|
|
106
|
-
Pass `signer["address"] = None` for rails without a wallet signer (Stripe SPT, card-only). The API responds with `signer_match["kind"] == "wallet_auth_requires_wallet_signing"` and a parsed `agent_instructions` block telling the agent to switch to `X-Operator-Token` auth
|
|
106
|
+
Pass `signer["address"] = None` for rails without a wallet signer (Stripe SPT, card-only). The API responds with `signer_match["kind"] == "wallet_auth_requires_wallet_signing"` and a parsed `agent_instructions` block telling the agent to switch to `X-Operator-Token` auth, spread the block directly into a 403 body.
|
|
107
107
|
|
|
108
108
|
### Credential Management
|
|
109
109
|
|
|
@@ -117,14 +117,14 @@ client.revoke_credential(cred["id"])
|
|
|
117
117
|
|
|
118
118
|
### Report an Agent's Wallet (Cross-Merchant Attribution)
|
|
119
119
|
|
|
120
|
-
After an agent authenticated via `operator_token` completes a payment, report the signer wallet so AgentScore can build a cross-merchant credential↔wallet profile. Fire-and-forget
|
|
120
|
+
After an agent authenticated via `operator_token` completes a payment, report the signer wallet so AgentScore can build a cross-merchant credential↔wallet profile. Fire-and-forget, `first_seen` is informational only. `network` is the key-derivation family: `"evm"` for any EVM chain (Base, Tempo, Ethereum, …) or `"solana"` for Solana.
|
|
121
121
|
|
|
122
122
|
```python
|
|
123
123
|
client.associate_wallet(
|
|
124
124
|
operator_token="opc_...",
|
|
125
125
|
wallet_address=signer_from_payment, # e.g. EIP-3009 `from` or Tempo MPP DID address
|
|
126
126
|
network="evm",
|
|
127
|
-
idempotency_key=payment_intent_id, # optional
|
|
127
|
+
idempotency_key=payment_intent_id, # optional, agent retries of the same payment no-op
|
|
128
128
|
)
|
|
129
129
|
```
|
|
130
130
|
|
|
@@ -161,8 +161,8 @@ with AgentScore(api_key="as_live_...") as client:
|
|
|
161
161
|
|
|
162
162
|
| Parameter | Default | Description |
|
|
163
163
|
|---------------|-----------------------------|--------------------------|
|
|
164
|
-
| `api_key` |
|
|
165
|
-
| `base_url` | `https://api.agentscore.
|
|
164
|
+
| `api_key` | _(required)_ | API key from [agentscore.com](https://www.agentscore.com); raises `ValueError` if missing |
|
|
165
|
+
| `base_url` | `https://api.agentscore.com` | API base URL |
|
|
166
166
|
| `timeout` | `10.0` | Request timeout (seconds)|
|
|
167
167
|
| `user_agent` | `None` | Prepended to the default `User-Agent` as `"{user_agent} (agentscore-py/{version})"`. Use to attribute API calls to your app. |
|
|
168
168
|
|
|
@@ -179,7 +179,7 @@ except AgentScoreError as e:
|
|
|
179
179
|
print(e.code, e.status_code, str(e))
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
-
`AgentScoreError.details` carries the rest of the response body
|
|
182
|
+
`AgentScoreError.details` carries the rest of the response body (`verify_url`, `linked_wallets`, `claimed_operator`, `actual_signer`, `expected_signer`, `reasons`, `agent_memory`) so callers can branch on granular denial codes without re-parsing.
|
|
183
183
|
|
|
184
184
|
### Typed error classes
|
|
185
185
|
|
|
@@ -188,11 +188,11 @@ For status-code-specific recovery, the SDK raises typed subclasses of `AgentScor
|
|
|
188
188
|
| Class | Triggered by | What it adds |
|
|
189
189
|
|---|---|---|
|
|
190
190
|
| `PaymentRequiredError` | HTTP 402 | The endpoint is not enabled for this account |
|
|
191
|
-
| `TokenExpiredError` | HTTP 401 with `error.code = "token_expired"` | Parsed body fields on the instance: `verify_url`, `session_id`, `poll_secret`, `poll_url`, `next_steps`, `agent_memory
|
|
192
|
-
| `InvalidCredentialError` | HTTP 401 with `error.code = "invalid_credential"` | Permanent
|
|
191
|
+
| `TokenExpiredError` | HTTP 401 with `error.code = "token_expired"` | Parsed body fields on the instance: `verify_url`, `session_id`, `poll_secret`, `poll_url`, `next_steps`, `agent_memory`, recover without re-parsing `details` |
|
|
192
|
+
| `InvalidCredentialError` | HTTP 401 with `error.code = "invalid_credential"` | Permanent, switch tokens or restart |
|
|
193
193
|
| `QuotaExceededError` | HTTP 429 with `error.code = "quota_exceeded"` | Account-level cap reached; don't retry |
|
|
194
194
|
| `RateLimitedError` | HTTP 429 with `error.code = "rate_limited"` | Per-second sliding-window cap; retry after `Retry-After` |
|
|
195
|
-
| `TimeoutError` | `httpx.TimeoutException` (connect/read/write/pool timeout) | Distinct from generic network errors. Note: subclasses `AgentScoreError`, **not** the builtin `TimeoutError
|
|
195
|
+
| `TimeoutError` | `httpx.TimeoutException` (connect/read/write/pool timeout) | Distinct from generic network errors. Note: subclasses `AgentScoreError`, **not** the builtin `TimeoutError`, import explicitly from `agentscore.errors` to disambiguate. |
|
|
196
196
|
|
|
197
197
|
All non-timeout `httpx.HTTPError` (ConnectError, ProtocolError, NetworkError, etc.) are wrapped as `AgentScoreError(code="network_error", status_code=0)`.
|
|
198
198
|
|
|
@@ -207,11 +207,11 @@ try:
|
|
|
207
207
|
except TokenExpiredError as e:
|
|
208
208
|
print("Verify at:", e.verify_url, "poll with:", e.poll_secret)
|
|
209
209
|
except QuotaExceededError as e:
|
|
210
|
-
print("Account quota reached
|
|
210
|
+
print("Account quota reached, surface to user; don't retry.")
|
|
211
211
|
except AgentScoreTimeoutError:
|
|
212
|
-
print("Network timeout
|
|
212
|
+
print("Network timeout, retry with backoff.")
|
|
213
213
|
except AgentScoreError as e:
|
|
214
|
-
print(e.code, e
|
|
214
|
+
print(e.code, str(e))
|
|
215
215
|
```
|
|
216
216
|
|
|
217
217
|
## Quota observability
|
|
@@ -224,10 +224,10 @@ quota = result.get("quota")
|
|
|
224
224
|
if quota and quota["limit"] and quota["used"]:
|
|
225
225
|
pct = (quota["used"] / quota["limit"]) * 100
|
|
226
226
|
if pct > 80:
|
|
227
|
-
print(f"AgentScore quota at {pct:.1f}
|
|
227
|
+
print(f"AgentScore quota at {pct:.1f}%, resets {quota['reset']}")
|
|
228
228
|
```
|
|
229
229
|
|
|
230
|
-
`quota` is absent when the API doesn't emit the headers (Enterprise / unlimited tiers). On a 429 response the SDK raises `QuotaExceededError` / `RateLimitedError` instead of returning a body, so `quota` is only readable on successful calls
|
|
230
|
+
`quota` is absent when the API doesn't emit the headers (Enterprise / unlimited tiers). On a 429 response the SDK raises `QuotaExceededError` / `RateLimitedError` instead of returning a body, so `quota` is only readable on successful calls; drive proactive alerting off the success-path field.
|
|
231
231
|
|
|
232
232
|
## Telemetry
|
|
233
233
|
|
|
@@ -235,7 +235,7 @@ if quota and quota["limit"] and quota["used"]:
|
|
|
235
235
|
|
|
236
236
|
## Documentation
|
|
237
237
|
|
|
238
|
-
- [API Reference](https://docs.agentscore.
|
|
238
|
+
- [API Reference](https://docs.agentscore.com)
|
|
239
239
|
|
|
240
240
|
## License
|
|
241
241
|
|
|
@@ -6,7 +6,7 @@ If you discover a security vulnerability, please report it responsibly.
|
|
|
6
6
|
|
|
7
7
|
**Do not open a public GitHub issue for security vulnerabilities.**
|
|
8
8
|
|
|
9
|
-
Instead, email us at **security@agentscore.
|
|
9
|
+
Instead, email us at **security@agentscore.com** with:
|
|
10
10
|
|
|
11
11
|
- Description of the vulnerability
|
|
12
12
|
- Steps to reproduce
|
|
@@ -17,6 +17,8 @@ from agentscore.types import (
|
|
|
17
17
|
AgentMemoryHint,
|
|
18
18
|
AgentMemoryIdentityPaths,
|
|
19
19
|
AgentSummary,
|
|
20
|
+
AipProvenance,
|
|
21
|
+
AipSignatureMaterial,
|
|
20
22
|
AssessResponse,
|
|
21
23
|
AssociateWalletResponse,
|
|
22
24
|
ChainEntry,
|
|
@@ -77,6 +79,8 @@ __all__ = [
|
|
|
77
79
|
"AgentScore",
|
|
78
80
|
"AgentScoreError",
|
|
79
81
|
"AgentSummary",
|
|
82
|
+
"AipProvenance",
|
|
83
|
+
"AipSignatureMaterial",
|
|
80
84
|
"AssessResponse",
|
|
81
85
|
"AssociateWalletResponse",
|
|
82
86
|
"ChainEntry",
|
|
@@ -27,7 +27,9 @@ _MAX_RETRY_WAIT_SECONDS = 10.0
|
|
|
27
27
|
def _retry_after_seconds(response: httpx.Response) -> float:
|
|
28
28
|
raw = response.headers.get("retry-after", "1")
|
|
29
29
|
try:
|
|
30
|
-
|
|
30
|
+
# Clamp the lower bound to 0: a negative ``Retry-After`` (buggy/malicious proxy)
|
|
31
|
+
# would otherwise reach ``time.sleep(-n)`` and raise ValueError, crashing the retry.
|
|
32
|
+
return max(0.0, min(float(raw), _MAX_RETRY_WAIT_SECONDS))
|
|
31
33
|
except (TypeError, ValueError):
|
|
32
34
|
return 1.0
|
|
33
35
|
|
|
@@ -131,6 +133,7 @@ if TYPE_CHECKING:
|
|
|
131
133
|
from collections.abc import Awaitable, Callable
|
|
132
134
|
|
|
133
135
|
from agentscore.types import (
|
|
136
|
+
AipSignatureMaterial,
|
|
134
137
|
AssessResponse,
|
|
135
138
|
AssociateWalletResponse,
|
|
136
139
|
CredentialCreateResponse,
|
|
@@ -151,12 +154,12 @@ class AgentScore:
|
|
|
151
154
|
def __init__(
|
|
152
155
|
self,
|
|
153
156
|
api_key: str,
|
|
154
|
-
base_url: str = "https://api.agentscore.
|
|
157
|
+
base_url: str = "https://api.agentscore.com",
|
|
155
158
|
timeout: float = 10.0,
|
|
156
159
|
user_agent: str | None = None,
|
|
157
160
|
):
|
|
158
161
|
if not api_key:
|
|
159
|
-
raise ValueError("AgentScore API key is required. Get one at https://agentscore.
|
|
162
|
+
raise ValueError("AgentScore API key is required. Get one at https://www.agentscore.com/sign-up")
|
|
160
163
|
self.api_key = api_key
|
|
161
164
|
self.base_url = base_url.rstrip("/")
|
|
162
165
|
self.timeout = timeout
|
|
@@ -260,18 +263,30 @@ class AgentScore:
|
|
|
260
263
|
policy: DecisionPolicy | None = None,
|
|
261
264
|
operator_token: str | None = None,
|
|
262
265
|
signer: Signer | None = None,
|
|
266
|
+
aip_token: str | None = None,
|
|
267
|
+
aip_signature: AipSignatureMaterial | None = None,
|
|
263
268
|
) -> AssessResponse:
|
|
264
269
|
"""Assess a wallet or operator (paid, writes score on-the-fly).
|
|
265
270
|
|
|
266
271
|
``signer`` opts into server-side wallet-signer-match: when supplied,
|
|
267
272
|
the API resolves the signer wallet against the claimed ``address`` and emits
|
|
268
273
|
a ``signer_match`` block on the response. See :class:`Signer`.
|
|
274
|
+
|
|
275
|
+
``aip_token`` (+ ``aip_signature``) supplies an AIP Agent Identity Token in place of
|
|
276
|
+
``address`` / ``operator_token``: the API re-verifies the issuer signature, the RFC 9421
|
|
277
|
+
proof-of-possession, and the attested claims, then evaluates policy against them.
|
|
269
278
|
"""
|
|
270
279
|
body: dict[str, Any] = {}
|
|
271
280
|
if address:
|
|
272
281
|
body["address"] = address
|
|
273
282
|
if operator_token:
|
|
274
283
|
body["operator_token"] = operator_token
|
|
284
|
+
# AIP Agent Identity Token path: the API re-verifies the IdP signature + claims
|
|
285
|
+
# server-side and evaluates policy against the attested identity.
|
|
286
|
+
if aip_token:
|
|
287
|
+
body["aip_token"] = aip_token
|
|
288
|
+
if aip_signature is not None:
|
|
289
|
+
body["aip_signature"] = dict(aip_signature)
|
|
275
290
|
if chain:
|
|
276
291
|
body["chain"] = chain
|
|
277
292
|
if refresh is not None:
|
|
@@ -398,17 +413,23 @@ class AgentScore:
|
|
|
398
413
|
policy: DecisionPolicy | None = None,
|
|
399
414
|
operator_token: str | None = None,
|
|
400
415
|
signer: Signer | None = None,
|
|
416
|
+
aip_token: str | None = None,
|
|
417
|
+
aip_signature: AipSignatureMaterial | None = None,
|
|
401
418
|
) -> AssessResponse:
|
|
402
419
|
"""Assess a wallet or operator (paid, writes score on-the-fly).
|
|
403
420
|
|
|
404
|
-
``signer`` opts into server-side wallet-signer-match
|
|
405
|
-
:meth:`assess`.
|
|
421
|
+
``signer`` opts into server-side wallet-signer-match; ``aip_token`` (+ ``aip_signature``)
|
|
422
|
+
supplies an AIP Agent Identity Token. Async mirror of :meth:`assess`.
|
|
406
423
|
"""
|
|
407
424
|
body: dict[str, Any] = {}
|
|
408
425
|
if address:
|
|
409
426
|
body["address"] = address
|
|
410
427
|
if operator_token:
|
|
411
428
|
body["operator_token"] = operator_token
|
|
429
|
+
if aip_token:
|
|
430
|
+
body["aip_token"] = aip_token
|
|
431
|
+
if aip_signature is not None:
|
|
432
|
+
body["aip_signature"] = dict(aip_signature)
|
|
412
433
|
if chain:
|
|
413
434
|
body["chain"] = chain
|
|
414
435
|
if refresh is not None:
|
|
@@ -34,7 +34,7 @@ class PaymentRequiredError(AgentScoreError):
|
|
|
34
34
|
class TokenExpiredError(AgentScoreError):
|
|
35
35
|
"""HTTP 401 with ``error.code = 'token_expired'`` — credential is no longer valid.
|
|
36
36
|
|
|
37
|
-
Covers both revoked and TTL-expired credentials; the API
|
|
37
|
+
Covers both revoked and TTL-expired credentials; the API does not distinguish
|
|
38
38
|
which. Body carries an auto-minted verification session — exposed here so callers recover
|
|
39
39
|
without re-parsing ``details``.
|
|
40
40
|
"""
|
|
@@ -191,7 +191,7 @@ class SignerMatch(TypedDict):
|
|
|
191
191
|
"""Server-side wallet-signer-match verdict.
|
|
192
192
|
|
|
193
193
|
Emitted on ``AssessResponse.signer_match`` when the request supplied
|
|
194
|
-
``signer``. Mirrors the verdict shape
|
|
194
|
+
``signer``. Mirrors the verdict shape the gate produces locally;
|
|
195
195
|
SDK consumers spread this into 403 bodies verbatim instead of re-deriving via 2
|
|
196
196
|
extra ``/v1/assess`` round trips.
|
|
197
197
|
|
|
@@ -218,7 +218,7 @@ class SignerMatch(TypedDict):
|
|
|
218
218
|
# Mirrors the top-level ``linked_wallets`` deny-guard — omitted on ``deny`` verdicts.
|
|
219
219
|
linked_wallets: NotRequired[list[str]]
|
|
220
220
|
# JSON-encoded ``{action, steps, user_message}`` envelope for SDK denial bodies.
|
|
221
|
-
#
|
|
221
|
+
# SDK consumers spread this into their 403
|
|
222
222
|
# body without re-parsing.
|
|
223
223
|
agent_instructions: NotRequired[str]
|
|
224
224
|
|
|
@@ -257,8 +257,7 @@ class SignerSanctionsUnavailable(TypedDict):
|
|
|
257
257
|
"""Server-side wallet-sanctions verdict — lookup itself failed.
|
|
258
258
|
|
|
259
259
|
Fail-closed: the gate denies whenever a signer was supplied and the OFAC
|
|
260
|
-
lookup couldn't resolve
|
|
261
|
-
strict-liability violation; falsely denying a clean buyer is bad UX.
|
|
260
|
+
lookup couldn't resolve, because OFAC sanctions screening is strict-liability.
|
|
262
261
|
"""
|
|
263
262
|
|
|
264
263
|
status: Literal["unavailable"]
|
|
@@ -268,10 +267,43 @@ class SignerSanctionsUnavailable(TypedDict):
|
|
|
268
267
|
SignerSanctions = SignerSanctionsClear | SignerSanctionsHit | SignerSanctionsUnavailable
|
|
269
268
|
|
|
270
269
|
|
|
270
|
+
class AipSignatureMaterial(TypedDict):
|
|
271
|
+
"""RFC 9421 HTTP Message Signature material proving possession of the AIT-bound ``cnf`` key.
|
|
272
|
+
|
|
273
|
+
Forwarded alongside ``aip_token`` so ``/v1/assess`` can re-verify proof-of-possession
|
|
274
|
+
authoritatively; the API never sees the original agent-to-merchant request itself.
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
method: str # HTTP method of the original agent-to-merchant request (``@method``)
|
|
278
|
+
authority: str # Authority/host the agent signed (``@authority``)
|
|
279
|
+
path: str # Request path the agent signed (``@path``)
|
|
280
|
+
signature_input: str # Raw ``Signature-Input`` header value the agent sent
|
|
281
|
+
signature: str # Raw ``Signature`` header value the agent sent
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class _AipProvenanceRequired(TypedDict):
|
|
285
|
+
issuer: str # Canonical issuer URL of the AIT
|
|
286
|
+
subject: str # The token's ``sub``: the IdP's subject identifier for the verified human
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class AipProvenance(_AipProvenanceRequired, total=False):
|
|
290
|
+
"""Provenance block returned when the identity input was an AIP Agent Identity Token.
|
|
291
|
+
|
|
292
|
+
Surfaces which issuer attested the identity and the trust level it asserted.
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
trust_level: Literal["autonomous", "human_present", "human_confirmed"]
|
|
296
|
+
agent_provider: str
|
|
297
|
+
# True when /v1/assess re-verified the RFC 9421 proof-of-possession. Always true on a
|
|
298
|
+
# success response: the API fail-closes with an HTTP 400/401 error (not a 200 deny)
|
|
299
|
+
# when possession can't be proven.
|
|
300
|
+
pop_verified: bool
|
|
301
|
+
|
|
302
|
+
|
|
271
303
|
class _AssessResponseRequired(TypedDict):
|
|
272
304
|
decision: str | None
|
|
273
305
|
decision_reasons: list[str]
|
|
274
|
-
identity_method: Literal["wallet", "operator_token"]
|
|
306
|
+
identity_method: Literal["wallet", "operator_token", "aip_token"]
|
|
275
307
|
|
|
276
308
|
|
|
277
309
|
class PolicyExplanation(TypedDict, total=False):
|
|
@@ -314,6 +346,8 @@ class AssessResponse(_AssessResponseRequired, total=False):
|
|
|
314
346
|
# Server-side OFAC SDN wallet-address verdict, returned only when the request supplied
|
|
315
347
|
# ``signer``. Empty otherwise.
|
|
316
348
|
signer_sanctions: NotRequired[SignerSanctions]
|
|
349
|
+
# Issuer provenance, returned only when ``identity_method == "aip_token"``.
|
|
350
|
+
aip: NotRequired[AipProvenance]
|
|
317
351
|
# Quota state for this account, captured from response headers on the success path.
|
|
318
352
|
# Use to monitor approach-to-cap proactively (warn at 80%, alert at 95%) before 429.
|
|
319
353
|
quota: NotRequired[QuotaInfo]
|
|
@@ -529,6 +563,7 @@ NextStepsAction = Literal[
|
|
|
529
563
|
class AgentMemoryIdentityPaths(TypedDict):
|
|
530
564
|
wallet: str
|
|
531
565
|
operator_token: str
|
|
566
|
+
agent_identity: NotRequired[str]
|
|
532
567
|
|
|
533
568
|
|
|
534
569
|
class AgentMemoryHint(TypedDict):
|
|
@@ -545,6 +580,8 @@ class AgentMemoryHint(TypedDict):
|
|
|
545
580
|
identity_check_endpoint: str
|
|
546
581
|
list_wallets_endpoint: NotRequired[str]
|
|
547
582
|
identity_paths: AgentMemoryIdentityPaths
|
|
583
|
+
# Issuer allowlist a merchant accepts for AIP Agent Identity Tokens, when advertised.
|
|
584
|
+
aip_trusted_issuers: NotRequired[list[str]]
|
|
548
585
|
bootstrap: str
|
|
549
586
|
do_not_persist_in_memory: list[str]
|
|
550
587
|
persist_in_credential_store: list[str]
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentscore-py"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.5.0"
|
|
8
8
|
description = "Python client for the AgentScore APIs"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -22,7 +22,7 @@ classifiers = [
|
|
|
22
22
|
]
|
|
23
23
|
|
|
24
24
|
[project.urls]
|
|
25
|
-
Homepage = "https://agentscore.
|
|
25
|
+
Homepage = "https://www.agentscore.com"
|
|
26
26
|
Repository = "https://github.com/agentscore/python-sdk"
|
|
27
27
|
Issues = "https://github.com/agentscore/python-sdk/issues"
|
|
28
28
|
|