> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentscore.com/llms.txt
> Use this file to discover all available pages before exploring further.

# AgentScore SDK (Python)

> Official Python client for AgentScore.

## Installation

```bash theme={"dark"}
pip install agentscore-py
```

## Quick start

```python theme={"dark"}
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
assessment = client.assess(
    "0xdb5aa553feeb2c3e3d03e8360b36fb0f7e480671",
    policy={"require_kyc": True},
)

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

## API reference

### Constructor

```python theme={"dark"}
AgentScore(
    api_key: str,
    base_url: str = "https://api.agentscore.com",
    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:

```python theme={"dark"}
client = AgentScore(api_key="...", user_agent="myapp/1.0")
# Outbound UA: "myapp/1.0 (agentscore-py/x.y.z)"
```

### `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`.

```python theme={"dark"}
# 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. Available on every tier, gated by monthly quota. Provide `address` (wallet) or `operator_token` (credential).

```python theme={"dark"}
# 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:

```python theme={"dark"}
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:

```python theme={"dark"}
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:

```python theme={"dark"}
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`:

```python theme={"dark"}
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.

```python theme={"dark"}
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.

```python theme={"dark"}
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.

```python theme={"dark"}
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.

```python theme={"dark"}
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.

```python theme={"dark"}
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.

```python theme={"dark"}
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`:

```python theme={"dark"}
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

```python theme={"dark"}
from agentscore import AgentScore

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

## Error handling

```python theme={"dark"}
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)
    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:

```python theme={"dark"}
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
```

## Links

* [PyPI](https://pypi.org/project/agentscore-py/)
* [GitHub](https://github.com/agentscore/python-sdk)
