Sandbox first: Use Agent quickstart for
paybond.agent()and CLI smoke with no signing keys. This tutorial covers the production intent lifecycle.
This path uses the runtime-neutral adapter in paybond_kit.agent_adapters. The same binding and adapter shown below can plug into provider SDKs, local-model runtimes, queues, and custom orchestrators that expose an application-owned tool executor.
What you will build
One guarded tool flow:
- Open a tenant-bound Paybond session.
- Create an intent for
travel.book_hotel. - Read the returned
capability_token. - Run the Paybond runtime-neutral adapter against the tool call.
- Execute the tool only if Harbor allows it.
- Submit signed evidence and inspect the resulting Harbor state.
Install
pip install paybond-kit
For a complete example application, see the reference implementation linked at the end of this page.
Required environment
Only PAYBOND_API_KEY comes from Paybond. The APP_* variables are example-local placeholders for values your app or trusted signer owns.
paybond-kit-login export APP_PRINCIPAL_DID="did:web:example.com#principal" export APP_PRINCIPAL_SEED_HEX="..." export APP_PAYEE_DID="did:web:example.com#hotel-booker" export APP_PAYEE_SEED_HEX="..." export APP_INTENT_CREATE_RECOGNITION_PROOF_JSON='{"key_id":"..."}' export APP_EVIDENCE_RECOGNITION_PROOF_JSON='{"key_id":"..."}' export APP_SETTLEMENT_RAIL="x402_usdc_base" # optional export APP_X402_PAYMENT_SIGNATURE="demo-signature" # optional
End-to-end flow
import asyncio import json import os from uuid import UUID, uuid4 from paybond_kit import ( Paybond, paybond_runtime_tool_call_adapter, ) def seed32_from_hex(env_name: str) -> bytes: raw = bytes.fromhex(os.environ[env_name]) if len(raw) != 32: raise ValueError(f"{env_name} must decode to exactly 32 bytes") return raw def required_json_env(env_name: str) -> dict[str, object]: value = json.loads(os.environ[env_name]) if not isinstance(value, dict): raise ValueError(f"{env_name} must contain a JSON object") return value async def book_hotel(city: str, nightly_budget_cents: int) -> dict[str, object]: return { "hotel": "Harbor House", "city": city, "status": "confirmed", "price_cents": nightly_budget_cents, "confirmation": "HB-2049", } async def main() -> None: paybond = await Paybond.open( api_key=os.environ["PAYBOND_API_KEY"], expected_environment="sandbox", ) try: intent_id = uuid4() created = await paybond.intents.create( principal_did=os.environ["APP_PRINCIPAL_DID"], principal_signing_seed=seed32_from_hex("APP_PRINCIPAL_SEED_HEX"), payee_did=os.environ["APP_PAYEE_DID"], budget={"currency": "usd", "max_spend_usd": 200}, predicate={ "version": 1, "root": { "op": "and", "clauses": [ { "op": "completion", "path": ["reservation", "status"], "value": "confirmed", }, { "op": "budget_cap", "path": ["reservation", "price_cents"], }, ], }, }, currency="usd", amount_cents=20_000, evidence_schema={"type": "object", "properties": {"reservation": {"type": "object"}}}, deadline_rfc3339="2030-12-31T23:59:59Z", allowed_tools=["travel.book_hotel"], recognition_proof=required_json_env("APP_INTENT_CREATE_RECOGNITION_PROOF_JSON"), settlement_rail=( "x402_usdc_base" if os.environ.get("APP_SETTLEMENT_RAIL") == "x402_usdc_base" else "stripe_connect" ), intent_id=intent_id, idempotency_key=f"intent:{intent_id}", ) capability_token = str(created.get("capability_token") or "") if not capability_token: raise RuntimeError("intent created without capability_token; ensure the intent is funded") guard = paybond.spend_guard(UUID(str(intent_id)), capability_token) run_tool = paybond_runtime_tool_call_adapter( guard, operation=lambda call: call["name"], requested_spend_cents=lambda call: int(call["requested_spend_cents"]), execute=lambda call: book_hotel( str(call["city"]), int(call["nightly_budget_cents"]), ), ) reservation = await run_tool( { "name": "travel.book_hotel", "requested_spend_cents": 18_700, "city": "Lisbon", "nightly_budget_cents": 18_700, } ) submitted = await paybond.intents.submit_evidence( UUID(str(intent_id)), {"reservation": reservation}, payee_did=os.environ["APP_PAYEE_DID"], payee_signing_seed=seed32_from_hex("APP_PAYEE_SEED_HEX"), recognition_proof=required_json_env("APP_EVIDENCE_RECOGNITION_PROOF_JSON"), idempotency_key=f"evidence:{intent_id}", ) print( { "intent_state": created["state"], "guard": "allow", "settlement_state": submitted["state"], "predicate_passed": submitted.get("predicate_passed"), } ) finally: await paybond.aclose() asyncio.run(main())
Where agent SDKs fit
The direct adapter call above uses the same objects that a real agent runtime uses:
paybond.spend_guard(...)carries(harbor, intent_id, capability_token).paybond_runtime_tool_call_adapter(...)performs HarborPOST /verify.- The adapter's
operationresolver becomes the Paybond operation string, sotravel.book_hotelmust appear in the intent allow-list exactly.
When you wire this into a live agent run, pass the runtime's tool-call object into the adapter and map denials with on_deny if the framework expects a structured tool error instead of an exception.
Stablecoin rail variant
Set APP_SETTLEMENT_RAIL="x402_usdc_base" to request the Base / USDC rail. The example keeps the commercial amount in USD (budget["currency"] == "usd", amount_cents, max_spend_usd) while Harbor handles x402 funding and settlement in USDC on Base.
If create does not return capability_token, the example calls paybond.intents.fund, prints the payment_required challenge, and retries after you provide APP_X402_PAYMENT_SIGNATURE.
Expected Harbor outcome
createmay returncapability_tokenimmediately, or the example may obtain it throughpaybond.intents.fundonx402_usdc_base.submit_evidencereturns a state pluspredicate_passed.- Release vs refund confirmation remains an operator-tier action in the current Kit surface. See Harbor API for settlement confirmation.
Common failure cases
- Missing
capability_token: the intent is not funded yet. - Missing
APP_X402_PAYMENT_SIGNATURE: Harbor returned an x402 challenge and is waiting for a signed retry. - Guardrail rejection:
allowed_toolsdid not match the tool name, spend exceeded the capability, or the token is invalid. - Tenant mismatch: treat as a critical tenant-isolation error; do not retry with another client or tenant ID.
- Evidence rejection: the payee seed does not match the payee identity bound on the intent, or the payload does not satisfy the predicate/schema.
Reference implementation
- Example app:
examples/paybond-kit-runtime-agent-python/app.py - Example README:
examples/paybond-kit-runtime-agent-python/README.md