Catalog PyPI
PACKAGE EXPLORERView in registry

agents-u-cash

PyPI

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.

402agent-paymentsagents.u.cashai-agentsapi-clientapi-monetizationautonomous-agentsbitcoinbitcoin-lightningcryptocrypto-paymentserc-20ethereumhttp-402lightning-networkllm
All keywords (31)
mcpmicropaymentsmodel-context-protocolmonetizationnon-custodialpay-per-requestpayment-gatewaypaymentsrbacsdkstablecoinsubagentsusdcweb3x402
Versions collected1
Direct dependencies0From release 0.9.0
Last observed20 Oct, 23:30 UTC

README

Version 0.9.0 documentation

agents-u-cash (Python)

PyPI version PyPI downloads Python versions license GitHub stars

Zero-dependency Python client for the 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 ~55 native coins and tokens (Bitcoin + Lightning, Ethereum + 16 EVM L2s incl. Robinhood Chain with tokenized NVDA/AAPL/TSLA/SPY, Solana, Tron, TON, XRP, USDT/USDC, USDC-on-Base gasless, UCASH) plus custom tokens (ERC-20 / TRC-20 / SPL / TON Jetton / Robinhood-20 / ...) - see 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. 33 chains (BTC + Lightning, ETH + 16 EVM L2s incl. Robinhood Chain, SOL, TRX, TON, XRP, USDC-on-Base, UCASH, custom tokens).
  • Zero-dependency Python client (3.7+, stdlib urllib) + a JavaScript SDK + an MCP server (84 tools).
  • Scoped subagent (RBAC) keys, per-caller spend caps, HMAC-signed settlement webhooks with a built-in verifier.
  • Sandbox: test keys + simulated confirmations (prime play has its own package: @ucash/prime).
  • 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 · OpenAPI · llms.txt · CHANGELOG

Contents

Install

pip install agents-u-cash

Or copy agents_u_cash.py - it has no dependencies.

Sell (as an agent)

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)

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.

Buy with x402 v2 (LF-spec wire)

The doors and facilitator also speak the current LF-spec v2 wire: PAYMENT-REQUIRED / PAYMENT-SIGNATURE / PAYMENT-RESPONSE headers, CAIP-2 networks (eip155:8453), atomic-string amounts, and gasless USDC-on-Base EIP-3009 authorization payments. The SDK handles the transport (header codec, submit, response parsing); the signature itself comes from your signer (e.g. stock @x402/evm, web3.py, or an EIP-712 implementation):

from agents_u_cash import AgentsUCash, decode_payment_header, encode_payment_header

buyer = AgentsUCash()
pr = buyer.get_payment_required(res_id)          # decodes the 402 headers
#    pr["accepts"][0] -> the CAIP-2 paymentRequirements; sign it with YOUR signer:
payment_payload = my_signer.create_payment_payload(2, pr["accepts"][0])   # e.g. @x402/evm ExactEvmScheme
payment_payload["resource"] = pr["payment_required"]["resource"]          # spec 7.1 wire shape
payment_payload["accepted"] = pr["accepts"][0]

result = buyer.pay_v2(res_id, payment_payload)   # sends PAYMENT-SIGNATURE
# -> {"status": 200, "settled": True, "payment_response": {"success", "transaction", "network", "payer"}}
# or a RESULT (never an exception) when refused/pending:
# -> {"status": 402, "settled": False, "error_reason": "insufficient_funds" | "settlement_pending" | ...}

check = buyer.exact_verify(payment_payload, pr["accepts"][0])    # facilitator verify (read-only)
# -> {"status", "is_valid", "payer", "invalid_reason"} - the RAW spec shape stock @x402 clients read
settled = buyer.exact_settle(payment_payload, pr["accepts"][0])  # submits for on-chain settlement
# -> {"status", "success", "transaction", "network", "payer", "error_reason"}

bazaar = buyer.list_discovery()   # GET /discovery/resources (Bazaar price manifests)
# -> {"x402Version": 2, "items": [...], "pagination": {"limit", "offset", "total"}}

Payment outcomes are results, not exceptions: pay_v2 returns settled=False + error_reason for refusals; only transport errors raise. https://agents.u.cash/v1/exact is also a complete stock-facilitator base, so unmodified @x402 clients work against the platform directly.

UCP checkout sessions (buyer)

Multi-item, mixed-currency carts over the Universal Commerce Protocol. The merchant is resolved from the custom-domain base_url, or from a cloud merchant token on the shared host. No API key.

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.

# 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)               # fee-credit 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).

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

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}]
agent.delete_resource(res["res_id"])                                         # soft-disable (settled history kept)
relay = agent.get_exact_relay()                                              # gasless relayer config (on by default)
agent.set_exact_relay(False)                                                 # opt out to buyer-pays-gas (gas + 21% while on)

Sandbox (test keys + simulated payments)

Test-mode keys and payment simulation for building against the API without real funds. Sandbox keys (sk_test_ secret / pk_test_ publishable) authenticate most read + test surfaces; live-only surfaces (webhook secrets) reject them with 403.

key = agent.create_sandbox_key(kind="secret", label="ci")   # the raw key shows ONCE
agent.get_sandbox_keys()                                   # masked hints + the ids for rotate/revoke
agent.rotate_sandbox_key(key_id)                           # fresh raw ONCE; the old one 401s immediately
agent.revoke_sandbox_key(key_id)

# a test resource that needs 3 simulated confirmations before it settles:
res = agent.create_resource(amount=5, test=True, test_confirmations=3)
chal = agent.create_challenge(res["res_id"])
agent.simulate_payment(chal["accepts"][0]["challengeId"], confirmations=3)   # settles the test payment

simulate_payment needs no key (the public challengeId authorizes it, like buyer verify).

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.

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:

CoinCodeNote
Bitcoinbtc
Bitcoin Lightningbtc_lnNOT lightning or btc-ln
Ethereumeth
EVM L2seth_base, eth_arb, eth_op, eth_linea, eth_unichain, eth_world, eth_scroll, eth_ink, eth_abstract, eth_plasma, eth_robinhoodplus native mnt, bera, s, mon, hype
Robinhood Chain equities<SYM>_robinhood for ALL 194 issuer-listed tokenstokenized assets (NVDA, AAPL, TSLA, SPY, MSFT, ... full list u.cash/equities); hosted equity checkouts require the buyer eligibility confirmation
USDC on Baseusdc_basethe x402 gasless rail
Stablecoinsusdc, usdt, usdt_tron, usdt_bsc
Otherssol, trx, xrp, ltc, doge, bnb, pol, avax, xmr, algo, bch, dot, xlm, xtz, ton, 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.

Resource vs payment request

Two ways to get paid - pick by use case:

create_resourcecreate_payment_request
Lifetimepersistent (payable many times)one-off link, closes on pay or expiry
Door/r/{res_id} / checkout_url/id/<enc> url
Multi-coin accepted_assetsyesno
Per-caller cap (max_per_caller)yesno
title / note / expiry / redirect / external_referencenoyes

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

API

MethodAuthDescription
signup(email, password, primary_wallet=None)-Register; returns api_key
top_up(amount)keyCreate a ≥$1 top-up checkout (adds platform credit; activates if not yet)
get_agent()keyAccount snapshot (balance, wallets, webhook, earnings summary)
set_webhook(url)keySet the settlement webhook (auto-generates the HMAC secret; shown once)
get_webhook() / rotate_webhook_secret() / clear_webhook()keyRead (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)keySet your receive address for an asset
set_stripe(secret_key, product_id, webhook_secret, publishable_key=None)keyConnect your Stripe account (card rail); verifies the key + product
get_stripe()keyMasked Stripe config + the webhook endpoint to register
clear_stripe()keyDisconnect your Stripe account
set_custom_token(type, code, contract_address, decimals, name, rate=None, rate_url=None)keyAdd a custom token (ERC-20/TRC-20/SPL/TON Jetton/Robinhood-20); then set_wallet(asset=code, address=...) to set its receive address
get_custom_tokens()keyList your custom tokens
delete_custom_token(code)keyRemove a custom token
get_settings()keyRead safe settings (confirmations, webhook url+secret, currency, payment prefs, notifications, branding)
set_settings(partial)keyPartially update safe settings
get_integrations()keyRead stored third-party integration credentials (Discord, Telegram, BigCommerce, Ecwid, Wix)
set_integrations(integrations)keyStore third-party integration credentials
create_resource(amount, currency=None, accepted_assets=None, webhook_url=None)keyCreate a priced resource
get_resources(res_id=None)keyList resources, or fetch one
create_challenge(res_id)keyBuild the multi-coin accepts[]
verify(challenge_id, hash)key optionalVerify + settle (buyer-push: no key needed)
get_settlements()keyEarnings log
view_door(res_id, html=False)-The public 402 door (JSON, or HTML)
get_payment_required(res_id)-Decode the door's v2 PAYMENT-REQUIRED (+ v1 header)
pay_v2(res_id, signature)-Send PAYMENT-SIGNATURE, parse PAYMENT-RESPONSE (results, not exceptions)
exact_verify(payload, reqs=None)-v2 facilitator verify (raw spec shape, read-only)
exact_settle(payload, reqs=None)-v2 facilitator settle (submits for on-chain settlement)
list_discovery(type='http', limit=None, offset=None)-Bazaar discovery: exact-rail price manifests
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(...)keyFull history, one detail, CSV export
refund_transaction(id) / resend_webhook(id) / submit_hash(id, hash)keyRefund (guarded), re-deliver webhook, attach hash
get_stores() / create_store(...) / update_store(id, ...) / delete_store(id)keyMulti-store CRUD
rotate_store_credential(id, which) / test_store_webhook(id)keyRotate 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)keyShop products (/v1/checkouts)
get_landers(...) / create_lander(checkout_id, tpl=None) / update_lander(...) / delete_lander(id)keyLanders + offer status
create_payout_info(...) / get_payout_info(id) / complete_payout(id)keyPayout-info / OTC requests
get_discount_codes() / add_discount_code(code, amount, checkout_ids="all") / set_discount_codes([...]) / delete_discount_code(code)keyDiscount codes (amount = multiplier)
get_custom_fields() / add_custom_field(**field) / set_custom_fields(custom_fields, title=None) / delete_custom_field(index)keyCheckout custom fields
get_billing() / buy_lander_pack(qty) / redeem_ucash(amount)keyBalances + capacity (lander pack, fee-credit redeem)
get_sandbox_keys() / create_sandbox_key(kind="secret", label=None) / rotate_sandbox_key(key_id) / revoke_sandbox_key(key_id)key (owner)Sandbox test-key lifecycle (the raw key shows exactly once)
simulate_payment(challenge_id, confirmations=None)- (buyer)Advance a test payment's simulated confirmations; settles it

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

Input contracts (strict)

The API validates every write before touching state, so nothing is ever silently ignored:

  • Unknown or misspelled keys -> 400 uxc_unknown_param listing the Allowed keys (this SDK's assertKeys raises the same error client-side before the request is sent).
  • Wrong shapes -> 400 uxc_invalid_request naming the field + expected shape, e.g. confirmations must be an object: {default, coins, increase}.
  • Booleans must be true/false (or the strings "true"/"false"/"1"/"0"); "yes"/"maybe" are 400s.
  • Updates are true partials: unsent fields keep their values (update_shop_product(id, {'price': 5}) changes only the price).
  • Re-setting an existing webhook secret returns has_secret: true + a hint; use rotate_webhook_secret() to mint + reveal a new one.

Error handling

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

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:

StatusMeaningSDK behavior
200successreturns response
202pending (x402 verified, awaiting on-chain settlement)returns response with status: 'pending' (does NOT raise)
402payment required / agent not activatedraises, .status = 402
410challenge expiredraises, .code = 'uxc_challenge_expired'
429rate limitedraises, .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:

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.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.9.02 Oct 20260

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
udotcash/agents

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 23:30 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:ef984cfc70fc3ffba76e7916483a1d751521a418d45c71ced28b5bbb40b29521