Skip to main content

Installation

pip install agentscore-py

Quick start

from agentscore import AgentScore

client = AgentScore(api_key="your-api-key")

# Summary lookup (free tier)
summary = client.get_reputation("0xdb5aa553feeb2c3e3d03e8360b36fb0f7e480671")
print(summary["score"]["value"], summary["score"]["grade"])

# On-the-fly assessment with decision (paid tier)
assessment = client.assess(
    "0xdb5aa553feeb2c3e3d03e8360b36fb0f7e480671",
    policy={"require_kyc": True},
)

if assessment["decision"] == "deny":
    print("Denied:", assessment["decision_reasons"])

API reference

Constructor

AgentScore(
    api_key: str,
    base_url: str = "https://api.agentscore.sh",
    timeout: float = 10.0,
    user_agent: str | None = None,  # custom prefix prepended to default UA
)
Set user_agent to prepend your app’s identifier so AgentScore support can trace outbound traffic back to you:
client = AgentScore(api_key="...", user_agent="myapp/1.0")
# Outbound UA: "myapp/1.0 (agentscore-py/2.1.2)"

client.get_reputation(address, chain=None)

Look up a wallet’s cached reputation profile. Free tier. Returns score, verification_level ("none", "wallet_claimed", or "kyc_verified"), chains array, and optionally operator_score, reputation, and agents.
# All chains (operator overview)
result = client.get_reputation("0x1234...")
print(result["score"]["value"], result["score"]["grade"])

# Filtered to a specific chain
result = client.get_reputation("0x1234...", chain="base")

client.assess(address=None, chain=None, refresh=False, policy=None, operator_token=None, signer=None)

On-the-fly trust assessment with policy evaluation. Paid tier only. Provide address (wallet) or operator_token (credential).
# With wallet address
result = client.assess(
    "0x1234...",
    policy={"require_kyc": True},
)

# With operator credential (non-wallet agents)
result = client.assess(
    operator_token="opc_abc123...",
    policy={"require_kyc": True, "min_age": 21},
)

# Original form still works
result = client.assess(
    "0x1234...",
    policy={
    },
    chain="base",    # optional
    refresh=False,   # optional, force recomputation
)

if result["decision"] == "deny":
    print("Denied:", result["decision_reasons"])

Server-side signer-match + OFAC SDN screening

Pass signer={"address", "network"} to opt into server-side wallet-signer-match and OFAC SDN wallet-address screening in a single round trip:
result = client.assess(
    "0xclaimed...",
    signer={"address": "0xsigner...", "network": "evm"},
    policy={"require_sanctions_clear": True},
)

# Wallet-binding verdict
match = result.get("signer_match")  # {"kind": ..., ...} or None
if match and match["kind"] == "wallet_signer_mismatch":
    # Signer wallet resolves to a different operator than the claimed address
    print("Mismatch:", match["expected_signer"], "vs", match["actual_signer"])

# OFAC SDN wallet-address verdict (discriminated union)
sanctions = result.get("signer_sanctions")
if sanctions and sanctions.get("sanctioned"):
    print("OFAC hit:", sanctions["ofac_label"], sanctions["sdn_uid"])
Set signer.address=None for rails with no wallet signer (Stripe SPT, card) — the response’s signer_match.kind will be wallet_auth_requires_wallet_signing. Wallet-OFAC SDN enforcement on the signer block is unconditional whenever a signer is supplied — no policy.require_sanctions_clear opt-in required. A signer_sanctions hit OR status: "unavailable" flips decision to deny 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. The aassess() async variant takes the same signer keyword.

Compliance assessment

Use compliance policy fields to enforce KYC, sanctions, age, and jurisdiction requirements:
result = client.assess(
    "0x1234...",
    policy={
        "require_kyc": True,
        "require_sanctions_clear": True,
        "min_age": 18,
        "blocked_jurisdictions": ["KP", "IR"],
    },
)

if result["decision"] == "deny":
    print("Denied:", result["decision_reasons"])
    print("Policy checks:", result["policy_result"]["checks"])

    # If denial is resolvable through verification, verify_url is present
    if result.get("verify_url"):
        print("Verification required:", result["verify_url"])

Quota observability

assess() / aassess() responses include an optional quota field captured from X-Quota-Limit / X-Quota-Used / X-Quota-Reset headers so callers can monitor approach-to-cap proactively before hitting 429:
result = client.assess("0x1234...", policy={"require_kyc": True})
quota = result.get("quota")
if quota and quota["limit"] and quota["used"]:
    pct = quota["used"] / quota["limit"]
    if pct >= 0.95:
        log.warning("AgentScore quota at %.0f%%; resets at %s", pct * 100, quota["reset"])
reset is an ISO-8601 timestamp or the literal string "never" for unlimited tiers; numeric fields are None when the API didn’t include the header.

Typed errors

The SDK raises subclasses of AgentScoreError so callers can branch on the class without parsing err.code:
from agentscore.errors import (
    AgentScoreError,
    InvalidCredentialError,
    PaymentRequiredError,
    QuotaExceededError,
    RateLimitedError,
    TimeoutError,
    TokenExpiredError,
)

try:
    result = client.assess(operator_token="opc_...", signer={"address": "0xs", "network": "evm"})
except TokenExpiredError as err:
    # 401 token_expired — credential revoked or TTL-expired. Body carries an auto-minted
    # verification session so the agent can recover without an API key.
    print("Verify URL:", err.verify_url, "session_id:", err.session_id, "poll_secret:", err.poll_secret)
except InvalidCredentialError:
    # 401 invalid_credential — operator_token doesn't exist; permanent state.
    ...
except QuotaExceededError:
    # 429 quota_exceeded — don't retry; wait for the quota reset window.
    ...
except RateLimitedError as err:
    # 429 rate_limited — short-window backoff. Retry after err.retry_after seconds.
    ...
except PaymentRequiredError:
    # 402 — vendor tier misconfig.
    ...
except AgentScoreError:
    # 5xx / network failures wrap to AgentScoreError(code="network_error", status_code=0).
    ...
On wallet_signer_mismatch responses (verdict, not exception), the SDK consumer can spread signer_match.agent_instructions (a JSON-encoded {action, steps, user_message} envelope) directly into a 403 body without re-deriving recovery copy.

client.create_session(context=None, product_name=None, address=None, operator_token=None)

Create a verification session for identity bootstrapping. Returns session details for agent polling. address and operator_token are optional pre-association hints; pass a wallet address to attach the session to a known wallet, or an existing opc_... to refresh KYC for an existing credential.
session = client.create_session(
    context="purchase",
    product_name="Premium Access",
)
print(session["session_id"])    # sess_...
print(session["poll_secret"])   # poll_...
print(session["verify_url"])    # URL for operator to visit
print(session["poll_url"])      # URL for agent to poll
The merchant name shown to operators on the verify page is derived from your account’s merchant name (Dashboard → Settings).

client.poll_session(session_id, poll_secret)

Poll a verification session. Returns operator_token when the operator completes verification.
result = client.poll_session(session["session_id"], session["poll_secret"])
if result["status"] == "verified":
    token = result["operator_token"]  # opc_... (returned once)
elif result["status"] == "pending":
    pass  # keep polling
elif result["status"] == "expired":
    pass  # session timed out

client.create_credential(label=None, ttl_days=None)

Create an operator credential. Default TTL is 24 hours, configurable 1-365 days.
cred = client.create_credential(label="my-agent", ttl_days=7)
print(cred["credential"])  # opc_... (shown once, save it)
print(cred["id"])           # UUID for management
print(cred["expires_at"])   # ISO timestamp

client.list_credentials()

List active (non-expired, non-revoked) credentials.
result = client.list_credentials()
for cred in result["credentials"]:
    print(cred["prefix"], cred["label"], cred["expires_at"])

client.revoke_credential(credential_id)

Revoke a credential by ID.
client.revoke_credential("credential-uuid")

client.associate_wallet(operator_token, wallet_address, network, idempotency_key=None)

Report a signer wallet that paid under a credential. Used by merchants after successful payment to build a cross-merchant credential↔wallet profile.
client.associate_wallet(
    operator_token="opc_...",
    wallet_address=signer_from_payment,  # EIP-3009 `from`, Tempo MPP DID, or Solana pubkey
    network="evm",                        # or "solana"
    idempotency_key=payment_intent_id,    # optional, retries no-op
)
Fire-and-forget. Response is {"associated": True, "first_seen": bool, "deduped"?: True}.

Async support

All methods have async counterparts prefixed with a:
import asyncio
from agentscore import AgentScore

async def main():
    async with AgentScore(api_key="your-api-key") as client:
        rep = await client.aget_reputation("0x1234...")
        print(rep["score"]["grade"])

        decision = await client.aassess("0x1234...", policy={"require_kyc": True})
        print(decision["decision"])

asyncio.run(main())
The async context manager (async with) automatically closes the HTTP client on exit.

Sync context manager

from agentscore import AgentScore

with AgentScore(api_key="your-api-key") as client:
    rep = client.get_reputation("0x1234...")
    print(rep["score"])

Error handling

from agentscore import AgentScore, AgentScoreError

client = AgentScore(api_key="your-api-key")

try:
    rep = client.get_reputation("0xinvalid")
except AgentScoreError as e:
    print(e.code)        # "invalid_address"
    print(e.status_code) # 400
    print(e.status)      # 400 (alias for .status_code, parity with node-sdk)
    print(str(e))        # error message
The .status property mirrors .status_code so polyglot codebases can use the same attribute name regardless of which SDK raised the error. AgentScoreError.details: dict[str, Any] carries response-body fields beyond {code, message}; verify_url, linked_wallets, claimed_operator, actual_signer, expected_signer, reasons, agent_memory; so callers can branch on granular denial codes without re-parsing:
try:
    client.assess("0xabc...", policy={"require_kyc": True})
except AgentScoreError as e:
    if e.code == "wallet_signer_mismatch":
        linked = e.details.get("linked_wallets", [])
        # re-sign from any address in `linked`, or switch to X-Operator-Token
    elif e.code == "token_expired":
        verify_url = e.details.get("verify_url")
        # surface verify_url to the user so they can re-verify