Skip to content

Commit 4354cc9

Browse files
vvillait88claude
andauthored
Add verification types, compliance policy, X-API-Key auth (TEC-177, TEC-179, TEC-182) (#6)
## Summary - Add verification types (VerificationLevel, OperatorVerification) - Add compliance policy fields (require_kyc, require_sanctions_clear, min_age, blocked_jurisdictions, require_entity_type) - Add operator_verification, verify_url, resolved_operator to assess response - Add verification_level to reputation response - Standardize auth to X-API-Key header (remove Authorization: Bearer) - Add dotenv setup for integration test env loading - Update README with compliance gating examples ## Tickets TEC-177, TEC-179, TEC-182 ## Test plan - [ ] All unit tests pass - [ ] Integration tests pass with API key in .env - [ ] Types match API response shapes 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 35ecca9 commit 4354cc9

10 files changed

Lines changed: 320 additions & 365 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ dist/
77
.ruff_cache/
88
.env
99
coverage/
10+
.coverage

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,20 @@ base_rep = client.get_reputation("0x1234...", chain="base")
2929
result = client.assess("0x1234...", policy={"min_grade": "B", "min_score": 35})
3030
print(result["decision"], result["decision_reasons"])
3131

32-
# Browse agents
33-
agents = client.get_agents(chain="base", limit=10)
34-
print(len(agents["items"]), agents["count"])
35-
36-
# Ecosystem stats
37-
stats = client.get_stats()
38-
print(stats["erc8004"]["known_agents"])
32+
# Compliance assessment with verification policy
33+
gated = client.assess("0x1234...", policy={
34+
"require_kyc": True,
35+
"require_sanctions_clear": True,
36+
"min_age": 21,
37+
})
38+
39+
if gated["decision"] == "deny":
40+
print(gated["decision_reasons"]) # ["kyc_required"]
41+
print(gated.get("verify_url")) # URL for operator verification
42+
43+
# Check verification level
44+
rep = client.get_reputation("0x1234...")
45+
print(rep.get("verification_level")) # "none" | "wallet_claimed" | "kyc_verified"
3946
```
4047

4148
### Async

agentscore/__init__.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,30 @@
33
from agentscore.client import AgentScore
44
from agentscore.errors import AgentScoreError
55
from agentscore.types import (
6-
AgentRecord,
7-
AgentsListResponse,
86
AssessResponse,
97
DecisionPolicy,
108
EntityType,
119
Grade,
10+
OperatorVerification,
1211
Reputation,
1312
ReputationResponse,
14-
ReputationResponseFull,
1513
ReputationStatus,
16-
StatsERC8004,
17-
StatsPayments,
18-
StatsReputation,
19-
StatsResponse,
14+
VerificationLevel,
2015
)
2116

2217
__version__ = _pkg_version("agentscore-py")
2318

2419
__all__ = [
25-
"AgentRecord",
2620
"AgentScore",
2721
"AgentScoreError",
28-
"AgentsListResponse",
2922
"AssessResponse",
3023
"DecisionPolicy",
3124
"EntityType",
3225
"Grade",
26+
"OperatorVerification",
3327
"Reputation",
3428
"ReputationResponse",
35-
"ReputationResponseFull",
3629
"ReputationStatus",
37-
"StatsERC8004",
38-
"StatsPayments",
39-
"StatsReputation",
40-
"StatsResponse",
30+
"VerificationLevel",
4131
"__version__",
4232
]

agentscore/client.py

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@
99

1010
if TYPE_CHECKING:
1111
from agentscore.types import (
12-
AgentsListResponse,
1312
AssessResponse,
1413
DecisionPolicy,
15-
ReputationResponseFull,
16-
StatsResponse,
14+
ReputationResponse,
1715
)
1816

1917

@@ -37,7 +35,7 @@ def __init__(
3735
def _headers(self) -> dict:
3836
return {
3937
"Accept": "application/json",
40-
"Authorization": f"Bearer {self.api_key}",
38+
"X-API-Key": self.api_key,
4139
"User-Agent": f"agentscore-py/{_pkg_version('agentscore-py')}",
4240
}
4341

@@ -86,7 +84,7 @@ def _handle_response(self, response: httpx.Response) -> dict:
8684

8785
# --- Sync methods ---
8886

89-
def get_reputation(self, address: str, chain: str | None = None) -> ReputationResponseFull:
87+
def get_reputation(self, address: str, chain: str | None = None) -> ReputationResponse:
9088
"""Get cached reputation for an address (free, read-only). Optionally filter by chain."""
9189
params: dict[str, str] = {}
9290
if chain:
@@ -114,22 +112,9 @@ def assess(
114112
response = client.post("/v1/assess", json=body)
115113
return self._handle_response(response)
116114

117-
def get_agents(self, **filters: Any) -> AgentsListResponse:
118-
"""Browse agents (free)."""
119-
params = {k: str(v).lower() if isinstance(v, bool) else str(v) for k, v in filters.items() if v is not None}
120-
client = self._get_sync_client()
121-
response = client.get("/v1/agents", params=params)
122-
return self._handle_response(response)
123-
124-
def get_stats(self) -> StatsResponse:
125-
"""Get ecosystem stats (free)."""
126-
client = self._get_sync_client()
127-
response = client.get("/v1/stats")
128-
return self._handle_response(response)
129-
130115
# --- Async methods ---
131116

132-
async def aget_reputation(self, address: str, chain: str | None = None) -> ReputationResponseFull:
117+
async def aget_reputation(self, address: str, chain: str | None = None) -> ReputationResponse:
133118
"""Get cached reputation for an address (free, read-only). Optionally filter by chain."""
134119
params: dict[str, str] = {}
135120
if chain:
@@ -157,19 +142,6 @@ async def aassess(
157142
response = await client.post("/v1/assess", json=body)
158143
return self._handle_response(response)
159144

160-
async def aget_agents(self, **filters: Any) -> AgentsListResponse:
161-
"""Browse agents (free)."""
162-
params = {k: str(v).lower() if isinstance(v, bool) else str(v) for k, v in filters.items() if v is not None}
163-
client = self._get_async_client()
164-
response = await client.get("/v1/agents", params=params)
165-
return self._handle_response(response)
166-
167-
async def aget_stats(self) -> StatsResponse:
168-
"""Get ecosystem stats (free)."""
169-
client = self._get_async_client()
170-
response = await client.get("/v1/stats")
171-
return self._handle_response(response)
172-
173145
def close(self):
174146
if self._sync_client:
175147
self._sync_client.close()

agentscore/types.py

Lines changed: 57 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
Grade = Literal["A", "B", "C", "D", "F"]
66
EntityType = Literal["agent", "service", "hybrid", "wallet", "bot", "unknown"]
7-
ReputationStatus = Literal["unknown", "known_unscored", "scored", "stale", "indexing"]
7+
ReputationStatus = Literal["scored", "stale", "known_unscored"]
8+
VerificationLevel = Literal["none", "wallet_claimed", "kyc_verified"]
89

910

1011
class Subject(TypedDict):
@@ -23,23 +24,26 @@ class Classification(TypedDict):
2324

2425

2526
class Score(TypedDict):
26-
value: int | None
27+
value: float | None
2728
grade: Grade | None
2829
scored_at: str | None
2930
status: ReputationStatus
3031
version: str
3132

3233

33-
class ChainScore(TypedDict):
34-
value: int | None
34+
class _ChainScoreRequired(TypedDict):
35+
value: float | None
3536
grade: Grade | None
36-
confidence: float | None
37-
dimensions: dict[str, float] | None
3837
scored_at: str | None
3938
status: ReputationStatus
4039
version: str
4140

4241

42+
class ChainScore(_ChainScoreRequired, total=False):
43+
confidence: float | None
44+
dimensions: dict[str, float] | None
45+
46+
4347
class Identity(TypedDict):
4448
ens_name: str | None
4549
website_url: str | None
@@ -71,36 +75,27 @@ class Reputation(TypedDict):
7175
last_feedback_at: str | None
7276

7377

74-
class EvidenceSummary(TypedDict, total=False):
75-
candidate_tx_count: int
76-
confirmed_or_likely_tx: int
77-
endpoint_count: int
78-
github_stars: int
79-
github_url: str | None
80-
has_a2a_agent_card: bool
81-
has_ens: bool
82-
has_github: bool
83-
has_website: bool
84-
healthy_endpoints: int
85-
is_erc8004: bool
78+
class EvidenceSummary(TypedDict):
8679
metadata_kind: str | None
87-
metadata_quality: str | None
88-
multi_chain_count: int
89-
reputation_feedback_count: int
90-
reputation_trust_avg: float | None
91-
reputation_uptime_avg: float | None
92-
reputation_activity_avg: float | None
93-
reputation_client_count: int
94-
verified_tx_count: int
80+
has_a2a_agent_card: bool
81+
website_url: str | None
82+
website_reachable: bool
9583
website_mentions_mcp: bool
9684
website_mentions_x402: bool
97-
website_reachable: bool
98-
website_url: str | None
85+
github_url: str | None
86+
github_stars: int | None
9987

10088

101-
class OperatorScore(TypedDict):
102-
score: int
89+
class RedactedClassification(TypedDict):
90+
entity_type: EntityType
91+
92+
93+
class _OperatorScoreRequired(TypedDict):
94+
score: float
10395
grade: Grade
96+
97+
98+
class OperatorScore(_OperatorScoreRequired, total=False):
10499
agent_count: int
105100
chains_active: list[str]
106101

@@ -109,20 +104,23 @@ class AgentSummary(TypedDict):
109104
token_id: int
110105
chain: str
111106
name: str | None
112-
score: int
107+
score: float
113108
grade: Grade
114109

115110

116-
class ChainEntry(TypedDict):
111+
class _ChainEntryRequired(TypedDict):
117112
chain: str
118113
score: ChainScore
119-
classification: Classification
114+
classification: Classification | RedactedClassification
115+
116+
117+
class ChainEntry(_ChainEntryRequired, total=False):
120118
identity: Identity
121119
activity: Activity
122120
evidence_summary: EvidenceSummary
123121

124122

125-
class ReputationResponse(TypedDict):
123+
class _ReputationResponseRequired(TypedDict):
126124
subject: Subject
127125
score: Score
128126
chains: list[ChainEntry]
@@ -131,19 +129,35 @@ class ReputationResponse(TypedDict):
131129
updated_at: str | None
132130

133131

134-
class ReputationResponseFull(ReputationResponse, total=False):
132+
class ReputationResponse(_ReputationResponseRequired, total=False):
133+
reputation: Reputation
135134
operator_score: OperatorScore
136135
agents: list[AgentSummary]
137-
reputation: Reputation
136+
verification_level: VerificationLevel
137+
138+
139+
class _OperatorVerificationRequired(TypedDict):
140+
level: VerificationLevel
141+
142+
143+
class OperatorVerification(_OperatorVerificationRequired, total=False):
144+
operator_type: str | None
145+
claimed_at: str | None
146+
verified_at: str | None
138147

139148

140149
class DecisionPolicy(TypedDict, total=False):
141150
min_grade: Grade
142151
min_score: int
143152
require_verified_payment_activity: bool
153+
require_kyc: bool
154+
require_sanctions_clear: bool
155+
min_age: int
156+
blocked_jurisdictions: list[str]
157+
require_entity_type: str
144158

145159

146-
class AssessResponse(TypedDict):
160+
class _AssessResponseRequired(TypedDict):
147161
subject: Subject
148162
score: Score
149163
chains: list[ChainEntry]
@@ -153,64 +167,12 @@ class AssessResponse(TypedDict):
153167
data_semantics: str
154168
caveats: list[str]
155169
updated_at: str | None
170+
171+
172+
class AssessResponse(_AssessResponseRequired, total=False):
156173
operator_score: OperatorScore | None
157174
reputation: Reputation | None
158175
agents: list[AgentSummary]
159-
160-
161-
class AgentRecord(TypedDict):
162-
chain: str
163-
token_id: int
164-
owner_address: str
165-
agent_wallet: str | None
166-
name: str | None
167-
description: str | None
168-
metadata_quality: str
169-
score: int | None
170-
grade: Grade | None
171-
entity_type: EntityType | None
172-
endpoint_count: int
173-
website_url: str | None
174-
github_url: str | None
175-
has_candidate_payment_activity: bool
176-
has_verified_payment_activity: bool
177-
agents_sharing_owner: int | None
178-
updated_at: str
179-
180-
181-
class AgentsListResponse(TypedDict):
182-
items: list[AgentRecord]
183-
next_cursor: str | None
184-
count: int
185-
version: str
186-
187-
188-
class StatsPayments(TypedDict):
189-
addresses_with_candidate_payment_activity: int
190-
addresses_with_verified_payment_activity: int
191-
total_candidate_transactions: int
192-
total_verified_transactions: int
193-
verification_status_summary: dict[str, int]
194-
195-
196-
class StatsERC8004(TypedDict):
197-
known_agents: int
198-
by_chain: dict[str, int]
199-
metadata_quality_distribution: dict[str, int]
200-
201-
202-
class StatsReputation(TypedDict):
203-
total_addresses: int
204-
scored_addresses: int
205-
entity_distribution: dict[str, int]
206-
score_distribution: dict[str, int]
207-
208-
209-
class StatsResponse(TypedDict, total=False):
210-
version: str
211-
as_of_time: str
212-
data_semantics: str
213-
erc8004: StatsERC8004
214-
reputation: StatsReputation
215-
payments: StatsPayments
216-
caveats: list[str]
176+
operator_verification: OperatorVerification
177+
resolved_operator: str
178+
verify_url: str

0 commit comments

Comments
 (0)