{"id":47993261,"url":"https://github.com/agentscore/python-sdk","last_synced_at":"2026-05-13T15:02:20.402Z","repository":{"id":345768882,"uuid":"1187154510","full_name":"agentscore/python-sdk","owner":"agentscore","description":"Python client for the AgentScore trust and reputation API","archived":false,"fork":false,"pushed_at":"2026-04-13T12:07:20.000Z","size":121,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-13T14:08:38.913Z","etag":null,"topics":["agentscore","ai-agent","blockchain","erc-8004","python","reputation","sdk","trust","x402"],"latest_commit_sha":null,"homepage":"https://docs.agentscore.sh","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/agentscore.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-20T12:02:10.000Z","updated_at":"2026-04-13T12:07:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/agentscore/python-sdk","commit_stats":null,"previous_names":["agentscore/python-sdk"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/agentscore/python-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentscore%2Fpython-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentscore%2Fpython-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentscore%2Fpython-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentscore%2Fpython-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/agentscore","download_url":"https://codeload.github.com/agentscore/python-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agentscore%2Fpython-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31954738,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"online","status_checked_at":"2026-04-18T02:00:07.018Z","response_time":103,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agentscore","ai-agent","blockchain","erc-8004","python","reputation","sdk","trust","x402"],"created_at":"2026-04-04T11:50:45.601Z","updated_at":"2026-05-13T15:02:20.396Z","avatar_url":"https://github.com/agentscore.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# agentscore-py\n\n[![PyPI version](https://img.shields.io/pypi/v/agentscore-py.svg)](https://pypi.org/project/agentscore-py/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\nPython client for the [AgentScore](https://agentscore.sh) APIs.\n\n## Install\n\n```bash\npip install agentscore-py\n```\n\n## Quick Start\n\n```python\nfrom agentscore import AgentScore\n\nclient = AgentScore(api_key=\"as_live_...\")\n\n# Look up cached reputation (free)\nrep = client.get_reputation(\"0x1234...\")\nprint(rep[\"score\"][\"value\"], rep[\"score\"][\"grade\"])\n\n# Filter to a specific chain\nbase_rep = client.get_reputation(\"0x1234...\", chain=\"base\")\n\n# Identity gate with policy (paid)\ngated = client.assess(\"0x1234...\", policy={\n    \"require_kyc\": True,\n    \"require_sanctions_clear\": True,\n    \"min_age\": 21,\n})\n\nif gated[\"decision\"] == \"deny\":\n    print(gated[\"decision_reasons\"])  # [\"kyc_required\"]\n    print(gated.get(\"verify_url\"))    # URL for operator verification\n\n# Check verification level\nrep = client.get_reputation(\"0x1234...\")\nprint(rep.get(\"verification_level\"))  # \"none\" | \"wallet_claimed\" | \"kyc_verified\"\n```\n\n### Credential-Based Identity\n\nAgents without wallets can use operator credentials for identity:\n\n```python\nresult = client.assess(operator_token=\"opc_...\")\nprint(result[\"decision\"])  # \"allow\" | \"deny\"\n```\n\n### Verification Sessions\n\nBootstrap identity for first-time agents. The success body carries structured `next_steps` (with `action: \"deliver_verify_url_and_poll\"`) and a cross-merchant `agent_memory` hint. Poll responses carry `next_steps.action` from the typed `NextStepsAction` Literal (`continue_polling`, `retry_merchant_request_with_operator_token`, `use_stored_operator_token`, `create_new_session`, `verification_failed`, `contact_support`).\n\n```python\nsession = client.create_session()\nprint(session[\"verify_url\"], session[\"poll_url\"], session[\"poll_secret\"])\nprint(session[\"next_steps\"][\"action\"])  # \"deliver_verify_url_and_poll\"\n\nstatus = client.poll_session(session[\"session_id\"], session[\"poll_secret\"])\nif status[\"status\"] == \"verified\":\n    print(status[\"operator_token\"])  # \"opc_...\" — use for future requests\n\n# Optional pre-association: attach the session to a known wallet or refresh KYC\n# for an existing operator credential.\nclient.create_session(address=\"0x...\")\nclient.create_session(operator_token=\"opc_...\")  # KYC refresh\n```\n\n### Wallet resolution\n\n`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 — network is auto-detected from the address shape.\n\n### Server-side signer-match + sanctions screening\n\nPass `signer={\"address\", \"network\"}` on `assess()` / `aassess()` to opt into server-side wallet-signer-match and OFAC SDN wallet-address screening on the same call. The response carries two new verdicts:\n\n```python\nresult = client.assess(\n    \"0xclaimed...\",\n    signer={\"address\": \"0xsigner...\", \"network\": \"evm\"},\n    policy={\"require_sanctions_clear\": True},\n)\n\n# signer_match: wallet-binding verdict\n#   kind: 'pass' | 'wallet_signer_mismatch' | 'wallet_auth_requires_wallet_signing'\n#   plus claimed_operator / signer_operator / expected_signer / actual_signer /\n#   linked_wallets / agent_instructions\nmatch = result.get(\"signer_match\")\nif match and match.get(\"kind\") == \"wallet_signer_mismatch\":\n    # signer wallet resolves to a different operator than the claimed address\n    ...\n\n# signer_sanctions: OFAC SDN wallet-address verdict (discriminated union)\n#   {\"status\": \"clear\"} | {\"sanctioned\": True, \"ofac_label\", \"sdn_uid\", \"listed_at\"}\n#   | {\"status\": \"unavailable\"}\nsanctions = result.get(\"signer_sanctions\")\nif sanctions and sanctions.get(\"sanctioned\"):\n    print(\"OFAC hit:\", sanctions[\"ofac_label\"], sanctions[\"sdn_uid\"])\n```\n\nUnder `policy.require_sanctions_clear`, the API flips `decision` to `deny` when `signer_sanctions` is `sanctioned: True` OR `status: \"unavailable\"` — `decision_reasons` will include `sanctions_flagged` or `sanctions_check_unavailable` respectively (fail-closed; OFAC strict-liability). Without the policy flag, both verdicts are informational.\n\nPass `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.\n\n### Credential Management\n\n```python\ncred = client.create_credential(label=\"my-agent\", ttl_days=7)\nprint(cred[\"credential\"])  # shown once\n\ncredentials = client.list_credentials()\nclient.revoke_credential(cred[\"id\"])\n```\n\n### Report an Agent's Wallet (Cross-Merchant Attribution)\n\nAfter 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.\n\n```python\nclient.associate_wallet(\n    operator_token=\"opc_...\",\n    wallet_address=signer_from_payment,  # e.g. EIP-3009 `from` or Tempo MPP DID address\n    network=\"evm\",\n    idempotency_key=payment_intent_id,  # optional — agent retries of the same payment no-op\n)\n```\n\n### Async\n\nAll methods have async variants prefixed with `a`:\n\n```python\nasync with AgentScore(api_key=\"as_live_...\") as client:\n    rep = await client.aget_reputation(\"0x1234...\")\n    result = await client.aassess(\"0x1234...\", policy={\"require_kyc\": True})\n\n    # Identity model methods\n    session = await client.acreate_session()\n    status = await client.apoll_session(session[\"session_id\"], session[\"poll_secret\"])\n    cred = await client.acreate_credential(label=\"my-agent\")\n    await client.alist_credentials()\n    await client.arevoke_credential(cred[\"id\"])\n    await client.aassociate_wallet(\n        operator_token=\"opc_...\",\n        wallet_address=\"0x...\",\n        network=\"evm\",\n    )\n```\n\n### Context Manager\n\n```python\nwith AgentScore(api_key=\"as_live_...\") as client:\n    rep = client.get_reputation(\"0x1234...\")\n```\n\n## Configuration\n\n| Parameter     | Default                     | Description              |\n|---------------|-----------------------------|--------------------------|\n| `api_key`     | `None`                      | API key from [agentscore.sh](https://agentscore.sh) |\n| `base_url`    | `https://api.agentscore.sh` | API base URL             |\n| `timeout`     | `10.0`                      | Request timeout (seconds)|\n| `user_agent`  | `None`                      | Prepended to the default `User-Agent` as `\"{user_agent} (agentscore-py/{version})\"`. Use to attribute API calls to your app. |\n\n`AgentScoreError.status` is a property aliasing `.status_code` so polyglot codebases can use the same attribute name regardless of which SDK raised the error.\n\n## Error Handling\n\n```python\nfrom agentscore import AgentScore, AgentScoreError\n\ntry:\n    rep = client.get_reputation(\"0xinvalid\")\nexcept AgentScoreError as e:\n    print(e.code, e.status_code, str(e))\n```\n\n`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.\n\n### Typed error classes\n\nFor status-code-specific recovery, the SDK raises typed subclasses of `AgentScoreError`. All inherit from `AgentScoreError` so existing `except AgentScoreError` still catches them.\n\n| Class | Triggered by | What it adds |\n|---|---|---|\n| `PaymentRequiredError` | HTTP 402 | The endpoint is not enabled for this account |\n| `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` |\n| `InvalidCredentialError` | HTTP 401 with `error.code = \"invalid_credential\"` | Permanent — switch tokens or restart |\n| `QuotaExceededError` | HTTP 429 with `error.code = \"quota_exceeded\"` | Account-level cap reached; don't retry |\n| `RateLimitedError` | HTTP 429 with `error.code = \"rate_limited\"` | Per-second sliding-window cap; retry after `Retry-After` |\n| `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. |\n\nAll non-timeout `httpx.HTTPError` (ConnectError, ProtocolError, NetworkError, etc.) are wrapped as `AgentScoreError(code=\"network_error\", status_code=0)`.\n\n```python\nfrom agentscore import (\n    AgentScore, AgentScoreError, TokenExpiredError, QuotaExceededError,\n)\nfrom agentscore.errors import TimeoutError as AgentScoreTimeoutError\n\ntry:\n    client.assess(\"0xabc...\", policy={\"require_kyc\": True})\nexcept TokenExpiredError as e:\n    print(\"Verify at:\", e.verify_url, \"poll with:\", e.poll_secret)\nexcept QuotaExceededError as e:\n    print(\"Account quota reached — surface to user; don't retry.\")\nexcept AgentScoreTimeoutError:\n    print(\"Network timeout — retry with backoff.\")\nexcept AgentScoreError as e:\n    print(e.code, e.message)\n```\n\n## Quota observability\n\n`assess()` (and `aassess()`) responses include an optional `quota` field captured from `X-Quota-Limit` / `X-Quota-Used` / `X-Quota-Reset` response headers. Use it to monitor approach-to-cap proactively (warn at 80%, alert at 95%) before a 429:\n\n```python\nresult = client.assess(\"0xabc...\", policy={\"require_kyc\": True})\nquota = result.get(\"quota\")\nif quota and quota[\"limit\"] and quota[\"used\"]:\n    pct = (quota[\"used\"] / quota[\"limit\"]) * 100\n    if pct \u003e 80:\n        print(f\"AgentScore quota at {pct:.1f}% — resets {quota['reset']}\")\n```\n\n`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.\n\n## Telemetry\n\n`telemetry_signer_match(payload)` and `atelemetry_signer_match(payload)` are fire-and-forget POSTs to `/v1/telemetry/signer-match` so AgentScore can track aggregate signer-binding behavior across merchants. Used internally by `agentscore-commerce`'s gate; available directly for custom integrations that perform their own wallet-signer-match checks.\n\n## Documentation\n\n- [API Reference](https://docs.agentscore.sh)\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagentscore%2Fpython-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fagentscore%2Fpython-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagentscore%2Fpython-sdk/lists"}