Metadata-Version: 2.4
Name: agents-u-cash
Version: 0.6.6
Summary: Monetize AI agents with non-custodial HTTP-402 crypto payments. Zero-dependency Python client for agents.u.cash: sell priced resources, get paid to your own wallet across 40+ chains, with scoped subagent keys.
Author: U.CASH
License: MIT
Project-URL: Homepage, https://agents.u.cash
Project-URL: Documentation, https://agents.u.cash
Project-URL: Changelog, https://github.com/UdotCASH/agents-u-cash/blob/main/CHANGELOG.md
Project-URL: Repository, https://github.com/UdotCASH/agents-u-cash
Project-URL: Bug Tracker, https://github.com/UdotCASH/agents-u-cash/issues
Keywords: agents.u.cash,x402,402,payments,crypto,non-custodial,mcp,ai-agents,subagents,rbac,api-client,sdk,http-402,crypto-payments,micropayments,bitcoin,ethereum,payment-gateway,llm,autonomous-agents,agent-payments,pay-per-request,monetization,api-monetization,usdc,stablecoin,bitcoin-lightning,lightning-network,erc-20,web3,model-context-protocol
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Office/Business :: Financial
Requires-Python: >=3.7
Description-Content-Type: text/markdown

# agents-u-cash (Python)

[![PyPI version](https://img.shields.io/pypi/v/agents-u-cash.svg?style=flat)](https://pypi.org/project/agents-u-cash/)
[![PyPI downloads](https://img.shields.io/pypi/dm/agents-u-cash.svg?style=flat)](https://pypi.org/project/agents-u-cash/)
[![Python versions](https://img.shields.io/pypi/pyversions/agents-u-cash.svg?style=flat)](https://pypi.org/project/agents-u-cash/)
[![license](https://img.shields.io/pypi/l/agents-u-cash.svg?style=flat)](https://github.com/UdotCASH/agents-u-cash/blob/main/LICENSE)
[![GitHub stars](https://img.shields.io/github/stars/UdotCASH/agents-u-cash.svg?style=flat)](https://github.com/UdotCASH/agents-u-cash)

Zero-dependency Python client for the [agents.u.cash](https://agents.u.cash) API - the 402 Online Protocol for non-custodial agent payments. Works for **both sides** of the network: an agent selling (manage resources, watch settlements) and a buyer (fetch a 402 door, pay, and settle - automatically via on-chain detection, or instantly by submitting the tx hash). Supports ~40 native coins (Bitcoin + Lightning, Ethereum + 15 EVM L2s, Solana, Tron, XRP, USDT/USDC, **USDC-on-Base** gasless, **UCASH**) plus custom tokens on 20 chains (ERC-20 / TRC-20 / SPL / ...) - see [agents.u.cash](https://agents.u.cash) for the full live list.

Uses only the standard library (`urllib`). No dependencies. Python 3.7+.

**Highlights**

- Monetize AI agents pay-per-call: sell priced resources over HTTP 402; buyers pay direct to your own wallet.
- Non-custodial: the platform never holds funds. 40+ chains (BTC + Lightning, ETH + 15 EVM L2s, SOL, TRX, XRP, USDC-on-Base, UCASH, custom tokens).
- Zero-dependency Python client (3.7+, stdlib `urllib`) + a JavaScript SDK + an MCP server (77 tools).
- Scoped subagent (RBAC) keys, per-caller spend caps, HMAC-signed settlement webhooks with a built-in verifier.
- Two-sided: sell (resources, one-off payment links, shop, stores, landers, payout-info/OTC) and buy (402 door, UCP checkout sessions).

**Docs:** [agents.u.cash](https://agents.u.cash) · [OpenAPI](https://agents.u.cash/openapi.json) · [llms.txt](https://agents.u.cash/llms.txt) · [CHANGELOG](https://github.com/UdotCASH/agents-u-cash/blob/main/CHANGELOG.md)

## Contents

- [Install](#install) · [Sell](#sell-as-an-agent) · [Buy](#buy-as-a-buyer) · [UCP checkout sessions](#ucp-checkout-sessions-buyer)
- [Manage your store](#manage-your-store-full-merchant-surface) · [Subagents](#subagents-scoped-rbac-keys) · [Spend caps](#spend-caps-per-caller-limits)
- [Verifying webhooks](#verifying-webhooks) · [Asset codes](#asset-codes) · [Resource vs payment request](#resource-vs-payment-request)
- [API](#api) · [Error handling](#error-handling)

## Install

```bash
pip install agents-u-cash
```

Or copy `agents_u_cash.py` - it has no dependencies.

## Sell (as an agent)

```python
from agents_u_cash import AgentsUCash

# Get an API key once (wallet-first; works at $0 immediately - verify email optionally for free credit):
# key = AgentsUCash().signup(email="me@agent.dev", password="longpass")["api_key"]

agent = AgentsUCash(api_key="...")
agent.set_wallet("btc", "bc1q…")
agent.set_webhook("https://my.bot/webhook")

res = agent.create_resource(amount=0.05)               # priced resource; res["checkout_url"] = pay.u.cash door
acc = agent.create_challenge(res["res_id"])             # what a buyer pays
# Share res["checkout_url"] - the pay.u.cash buyer door (/checkout/<enc>?cloud=<token>):
# a human opens the HTML checkout, an agent fetches it + "?agent=1" for the 402 manifest,
# an x402 client sends X-PAYMENT. (https://agents.u.cash/r/{res_id} is a back-compat alias.)
# Exact vs dust: save 3+ addresses per coin (agent.set_wallet("ucash", "0xaaa...,0xbbb...,0xccc..."))
# for exact-amount unique-address payments (500.00000000); one address adds sub-unit dust (500.00004180).

# One-off link instead of a persistent resource (closes when paid or after expiry):
req = agent.create_payment_request(amount=5, expiry="24h")   # -> {"url": "https://pay.u.cash/id/<enc>"}

settled = agent.get_settlements()                       # your earnings log
```

## Buy (as a buyer)

```python
from agents_u_cash import AgentsUCash

buyer = AgentsUCash()                                   # no key needed to buy
door = buyer.view_door(res_id)                          # the 402 door (JSON)
# 1. pick an entry, pay entry["payTo"] exactly entry["amount"] from your wallet (out of band)
# 2. the platform auto-detects the on-chain payment and settles it.
#    Optionally POST the tx hash to settle instantly instead:
result = buyer.verify(door["accepts"][0]["challengeId"], tx_hash)
# -> {"settled": True} | {"status": "pending", "confirmations": n, "required": m}
```

A human-friendly payable page is also available: `buyer.view_door(res_id, html=True)` returns the HTML.

## UCP checkout sessions (buyer)

Multi-item, mixed-currency carts over the [Universal Commerce Protocol](https://ucp.dev). The merchant is resolved from the custom-domain `base_url`, or from a `cloud` merchant token on the shared host. No API key.

```python
buyer = AgentsUCash()   # base_url = the merchant's domain (or the platform host + cloud)
cart = buyer.create_checkout(
    line_items=[{"item": {"id": res_id_a}, "quantity": 1}, {"item": {"id": res_id_b}, "quantity": 2}],
    currency="USD",            # optional: cart currency for mixed-currency carts
    cloud="<merchant-token>",  # only on the shared platform host
)
# -> {"id": ..., "status": "incomplete", "currency", "line_items", "totals", "ap2": {"merchant_authorization", "nonce"}}
ready = buyer.complete_checkout(cart["id"], cloud="<merchant-token>")
# -> ready_for_complete + payment_handlers[] (pay each challenge on-chain)
order = buyer.get_order(cart["id"], cloud="<merchant-token>")   # per-item fulfillment status
```

Optional AP2 (`dev.ucp.shopping.ap2_mandate`): pass `complete_checkout(id, ap2={"checkout_mandate": ...}, cloud=...)` with a buyer-signed SD-JWT-VC for holder-proof authorization. Responses are RFC 9421-signed (ES256) with the merchant key.

## Manage your store (full merchant surface)

Beyond priced 402 resources, an agent is a first-class merchant over its own account: full transaction history + actions, multi-store, shop products, landers, payout-info/OTC, discount codes, checkout custom fields, and billing. All key-authenticated; tenant-scoped to the agent.

```python
# Transactions: full history, CSV export, + actions
txs = agent.get_transactions(status="C", limit=50)
csv = agent.download_transactions(date_from="2026-01-01")     # raw CSV text
agent.refund_transaction(tx_id)        # self-guarding: only if you connected a refund-capable node/coinbase
agent.resend_webhook(tx_id)
agent.submit_hash(tx_id, "0xabc...")

# Stores (sub-merchants)
store = agent.create_store(label="Store B")  # api_key+cloud_token+webhook_secret returned once
agent.rotate_store_credential(store["store"]["id"], "cloud_token")

# Shop products, landers, payout-info/OTC
agent.create_shop_product(title="Ebook", price=9.99, currency="USD")
agent.create_lander(checkout_id=flag_id)
agent.create_payout_info(amount=50, currency="USD", email="payee@x.dev")  # emails the payee a link

# Discount codes (amount = price multiplier: 0.9 = 10% off) + checkout custom fields
agent.add_discount_code(code="LAUNCH", amount=0.9, checkout_ids="all")
agent.add_custom_field(type="select", label="Size", options=["S", "M", "L"])

# Billing: balances + capacity
bill = agent.get_billing()             # {credit_balance, ucash_points, lander_slots:{...}}
agent.buy_lander_pack(10)              # debits credit_balance, grows slots
agent.redeem_ucash(1000)               # UCASH points -> fee credit
```

## Subagents (scoped RBAC keys)

Delegate a LIMITED credential to another automated principal. A subagent is a scoped `sa_` API key with a
staff role: it authenticates against your tenant, but the existing RBAC (`uxc_can`) enforces a limited
capability set on every endpoint, so it can only do what the role allows. Owner-only (you create them; a
subagent cannot create subagents). Reuses the merchant staff model + seat billing (a subagent with no free
slot debits `credit_balance`).

```python
# Built-in role: a clerk can create resources + read, but not edit settings, refund, or manage stores
sub = agent.create_subagent(role="clerk", display_name="Fulfillment bot")
# sub["api_key"] is the sa_ key, returned ONCE. The key works on every /v1/* endpoint; writes its role
# lacks return 403, and managing stores stays owner-only.

# Or a custom permission set (slugs: transactions.view, checkouts.edit, payment-links.create, ...).
# store_scope (store ids) restricts it to those stores (empty = all stores).
ro = agent.create_subagent(
    role="custom", permissions=["transactions.view", "checkouts.view"], store_scope=[store_id]
)

agent.get_subagents()                                  # list (never returns the api_key)
agent.update_subagent(sub["subagent"]["id"], role="manager", status="suspended")
fresh = agent.rotate_subagent_key(sub["subagent"]["id"])   # old key stops working; new key ONCE
agent.delete_subagent(sub["subagent"]["id"])           # key stops working; seat slot frees
```

## Spend caps (per-caller limits)

Cap how much a single payer can spend on a resource in a rolling window. A payer whose settled spend in the
window reaches `amount` is refused new authorization (x402 before-charge; on detect the platform refuses to
credit an over-cap payment). Caller identity is the payer wallet (or, with `by: 'ip'`, the buyer IP, enforced at the door before any charge). Optional; off by default.

```python
res = agent.create_resource(amount=0.05, currency="USD", max_per_caller={"amount": 1.00, "window_hours": 24})  # add by="ip" to cap by buyer IP at the door instead of by wallet
agent.set_resource_cap(res["res_id"], {"amount": 0.50, "window_hours": 6})  # change it
agent.set_resource_cap(res["res_id"], None)                                  # clear it
callers = agent.get_resource_callers(res["res_id"])                          # [{caller, spend, payments}]
```

## Verifying webhooks

When a payment settles, agents.u.cash POSTs an HMAC-signed event to your webhook URL. Verify it with the
static helper (it runs on your server; no instance or API key needed). The signature is HMAC-SHA256 of
`"<t>.<raw_body>"` in the `X-Webhook-Signature: t=<unix>,v1=<hex>` header. **Use the raw request body** -
re-encoding the JSON breaks the signature.

```python
import json, os
from agents_u_cash import AgentsUCash

# Flask - use the RAW body (request.get_data()), NOT request.get_json():
@app.post("/webhook")
def webhook():
    valid = AgentsUCash.verify_webhook_signature(
        request.get_data(),                          # the EXACT raw bytes you received
        request.headers.get("X-Webhook-Signature"),  # t=<unix>,v1=<hex>
        os.environ["UXC_WEBHOOK_SECRET"],            # from set_webhook()/rotate_webhook_secret() - shown ONCE
    )
    if not valid:
        return "bad signature", 401
    event = json.loads(request.get_data())
    # Deduplicate by event["event_id"] (also in X-Webhook-Event-Id) - a settled txn may be delivered >1x.
    return "ok", 200
```

The 300-second replay window is on by default; pass `tolerance=0` to skip the freshness check.

## Asset codes

`accepted_assets` takes coin codes (omit it to accept all your configured wallets). Common built-in codes:

| Coin | Code | Note |
|---|---|---|
| Bitcoin | `btc` | |
| Bitcoin Lightning | `btc_ln` | NOT `lightning` or `btc-ln` |
| Ethereum | `eth` | |
| EVM L2s | `eth_base`, `eth_arb`, `eth_op`, `eth_linea`, `eth_unichain`, `eth_world`, `eth_scroll`, `eth_ink`, `eth_abstract`, `eth_plasma` | plus native `mnt`, `bera`, `s`, `mon`, `hype` |
| USDC on Base | `usdc_base` | the x402 gasless rail |
| Stablecoins | `usdc`, `usdt`, `usdt_tron`, `usdt_bsc` | |
| Others | `sol`, `trx`, `xrp`, `ltc`, `doge`, `bnb`, `pol`, `avax`, `xmr`, `algo`, `bch`, `dot`, `xlm`, `xtz`, `ucash` | |

Plus any custom-token `code` you added via `set_custom_token()`. A misspelled code raises
`AgentsUCashError` with `.code = 'uxc_unknown_asset'` ("Unsupported asset: X"). The live canonical list
grows as chains are added - see [agents.u.cash](https://agents.u.cash).

## Resource vs payment request

Two ways to get paid - pick by use case:

| | `create_resource` | `create_payment_request` |
|---|---|---|
| Lifetime | persistent (payable many times) | one-off link, closes on pay or expiry |
| Door | `/r/{res_id}` / `checkout_url` | `/id/<enc>` `url` |
| Multi-coin `accepted_assets` | yes | no |
| Per-caller cap (`max_per_caller`) | yes | no |
| `title` / `note` / `expiry` / `redirect` / `external_reference` | no | yes |

A resource is a standing price buyers pay repeatedly; a payment request is a single invoice link.

## API

| Method | Auth | Description |
|---|---|---|
| `signup(email, password, primary_wallet=None)` | - | Register; returns `api_key` |
| `top_up(amount)` | key | Create a ≥$1 top-up checkout (adds platform credit; activates if not yet) |
| `get_agent()` | key | Account snapshot (balance, wallets, webhook, earnings summary) |
| `set_webhook(url)` | key | Set the settlement webhook (auto-generates the HMAC secret; shown once) |
| `get_webhook()` / `rotate_webhook_secret()` / `clear_webhook()` | key | Read (masked) / rotate / clear the webhook |
| `AgentsUCash.verify_webhook_signature(raw_body, signature_header, secret, tolerance=300)` | - | Verify an incoming webhook's HMAC signature (static; runs on your server) |
| `set_wallet(asset, address)` | key | Set your receive address for an asset |
| `set_stripe(secret_key, product_id, webhook_secret, publishable_key=None)` | key | Connect your Stripe account (card rail); verifies the key + product |
| `get_stripe()` | key | Masked Stripe config + the webhook endpoint to register |
| `clear_stripe()` | key | Disconnect your Stripe account |
| `set_custom_token(type, code, contract_address, decimals, name, rate=None, rate_url=None)` | key | Add a custom token (ERC-20/TRC-20/SPL); then `set_wallet(asset=code, address=...)` to set its receive address |
| `get_custom_tokens()` | key | List your custom tokens |
| `delete_custom_token(code)` | key | Remove a custom token |
| `get_settings()` | key | Read safe settings (confirmations, webhook url+secret, currency, payment prefs, notifications, branding) |
| `set_settings(partial)` | key | Partially update safe settings |
| `get_integrations()` | key | Read stored third-party integration credentials (Discord, Telegram, BigCommerce, Ecwid, Wix) |
| `set_integrations(integrations)` | key | Store third-party integration credentials |
| `create_resource(amount, currency=None, accepted_assets=None, webhook_url=None)` | key | Create a priced resource |
| `get_resources(res_id=None)` | key | List resources, or fetch one |
| `create_challenge(res_id)` | key | Build the multi-coin `accepts[]` |
| `verify(challenge_id, hash)` | key optional | Verify + settle (buyer-push: no key needed) |
| `get_settlements()` | key | Earnings log |
| `view_door(res_id, html=False)` | - | The public 402 door (JSON, or HTML) |
| `create_checkout(line_items, currency=None, buyer=None, context=None, cloud=None)` | - | UCP checkout session (multi-item, mixed-currency cart) |
| `get_checkout(id, cloud=None)` | - | Fetch a checkout session |
| `complete_checkout(id, ap2=None, cloud=None)` | - | Mint challenges -> ready_for_complete (optional AP2 mandate) |
| `cancel_checkout(id, cloud=None)` | - | Cancel a checkout session |
| `get_order(id, cloud=None)` | - | A checkout session as a UCP order (per-item fulfillment) |
| `search_catalog(query=None, filters=None, pagination=None, cloud=None)` | - | Search the merchant catalog |
| `get_product(id, cloud=None)` | - | Fetch a single catalog product by id |
| `lookup_products(ids, cloud=None)` | - | Batch catalog lookup by ids |
| `get_transactions(...)` / `get_transaction(id, webhook_log=False)` / `download_transactions(...)` | key | Full history, one detail, CSV export |
| `refund_transaction(id)` / `resend_webhook(id)` / `submit_hash(id, hash)` | key | Refund (guarded), re-deliver webhook, attach hash |
| `get_stores()` / `create_store(...)` / `update_store(id, ...)` / `delete_store(id)` | key | Multi-store CRUD |
| `rotate_store_credential(id, which)` / `test_store_webhook(id)` | key | Rotate a store credential; send a test webhook |
| `get_shop_products(id=None)` / `create_shop_product(**fields)` / `update_shop_product(id, **fields)` / `delete_shop_product(id)` | key | Shop products (`/v1/checkouts`) |
| `get_landers(...)` / `create_lander(checkout_id, tpl=None)` / `update_lander(...)` / `delete_lander(id)` | key | Landers + offer status |
| `create_payout_info(...)` / `get_payout_info(id)` / `complete_payout(id)` | key | Payout-info / OTC requests |
| `get_discount_codes()` / `add_discount_code(code, amount, checkout_ids="all")` / `set_discount_codes([...])` / `delete_discount_code(code)` | key | Discount codes (amount = multiplier) |
| `get_custom_fields()` / `add_custom_field(**field)` / `set_custom_fields(custom_fields, title=None)` / `delete_custom_field(index)` | key | Checkout custom fields |
| `get_billing()` / `buy_lander_pack(qty)` / `redeem_ucash(amount)` | key | Balances + capacity (lander pack, UCASH redeem) |

All calls return the parsed `response` dict and raise `AgentsUCashError` on API errors (which carries `.code` and `.status`).

## Error handling

All calls return the parsed `response` dict on success and raise `AgentsUCashError` on errors; the error
carries `.code` and `.status`.

```python
from agents_u_cash import AgentsUCash, AgentsUCashError

try:
    agent.create_resource(amount=0.05)
except AgentsUCashError as e:
    print(e.code, e.status, e)   # e.g. 'uxc_agent_not_activated' 402 ...
```

Status codes you will see:

| Status | Meaning | SDK behavior |
|---|---|---|
| 200 | success | returns `response` |
| 202 | pending (x402 verified, awaiting on-chain settlement) | returns `response` with `status: 'pending'` (does NOT raise) |
| 402 | payment required / agent not activated | raises, `.status = 402` |
| 410 | challenge expired | raises, `.code = 'uxc_challenge_expired'` |
| 429 | rate limited | raises, `.status = 429` |

`verify(challenge_id, hash)` returns `{"settled": True}` on success,
`{"status": "pending", "confirmations": n, "required": m}` while the payment awaits on-chain
confirmation, or `{"status": "underpaid"}`. An expired challenge raises with `.code === 'uxc_challenge_expired'`
(HTTP 410 Gone) - catch on `.code`:

```python
try:
    r = buyer.verify(challenge_id, tx_hash)
except AgentsUCashError as e:
    if e.code == "uxc_challenge_expired":
        ...  # re-fetch accepts[] and retry
    else:
        raise
```

Non-custodial: the platform never holds funds - every `payTo` is the seller's own wallet, and this client never sees your wallet keys.
