Python quickstart
This quickstart walks through the canonical Paybond Kit flow in Python:
- Open an authenticated session against the Gateway with a service-account API key.
- Verify a Biscuit capability token scoped to a funded intent.
- Submit signed evidence so Harbor can evaluate the predicate and transition the intent.
Every call below is tenant-scoped: the realm is derived from the Gateway exchange and bound to the returned client. You never pass a tenant id by hand, and the Kit will fail closed if Harbor ever echoes a different tenant or intent than you requested.
allowed_tools should contain your application's own tool or operation names. Paybond does not define a fixed catalog here; Harbor simply enforces that the later verify_capability(..., operation=...) call matches one of the names you bound onto the intent.
For the full API surface, see the Python SDK reference.
Prerequisites
- Python 3.11+.
- A
paybond_sk_…service-account API key issued for your tenant realm. - The Gateway and Harbor base URLs reachable from your runtime (for example
https://gateway.example.com,https://harbor.example.com). - For the principal-signed intent create flow: a 32-byte Ed25519 seed for the principal and a 32-byte seed for the payee bound to the intent. Intent creation and evidence signing also require the bundled native extension built (see Install).
Install
Install the published package:
pip install paybond-kitOptional extras:
pip install "paybond-kit[agents,langgraph]"Published wheels bundle the paybond_kit._native extension used for principal-side intent signing and payee evidence signing.
For local development from a checkout:
cd kit/python
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
maturin developmaturin develop is only required for source builds from this repository. Without the bundled native extension, paybond.intents.create and paybond.intents.submit_evidence raise ImportError at call time.
Authenticated session
Paybond.open exchanges your service-account API key for a short-lived Harbor JWT at the Gateway (POST /v1/auth/harbor-access), caches the tenant realm echoed by the response, and returns a tenant-bound async Harbor client.
import asyncio
import os
from paybond_kit import Paybond
async def main() -> None:
paybond = await Paybond.open(
gateway_base_url="https://gateway.example.com",
api_key=os.environ["PAYBOND_API_KEY"],
harbor_base_url="https://harbor.example.com",
)
try:
print("tenant realm:", paybond.harbor.tenant_id)
finally:
await paybond.aclose()
asyncio.run(main())Key properties of the session:
- Tenant is derived, not supplied. The tenant realm is whatever the Gateway echoes. Do not read the tenant from request bodies or environment variables at call time.
- Token rotation is automatic. Each Harbor call awaits a cached JWT and refreshes before expiry. Force rotation with
await paybond.rotate_harbor_token()during a credential rotation drill. - Sessions are single-tenant. Construct one
Paybondinstance per(tenant, service account). Never share across tenants or reuse across concurrent agent runs bound to different intents. - Release resources. Always await
paybond.aclose()so the underlyinghttpx.AsyncClientdrains.Paybond.openraisesGatewayAuthErroron any gateway or token failure.
Verify and submit evidence walkthrough
Once you have a funded intent_id and a Biscuit capability token minted for the payee, the tenant-safe integration pattern is: verify the capability, run the tool work, then submit signed evidence.
import asyncio
import os
from datetime import datetime, timezone
from uuid import UUID
from paybond_kit import Paybond, PaybondCapabilityBinding
async def main() -> None:
paybond = await Paybond.open(
gateway_base_url="https://gateway.example.com",
api_key=os.environ["PAYBOND_API_KEY"],
harbor_base_url="https://harbor.example.com",
)
try:
intent_id = UUID(os.environ["PAYBOND_INTENT_ID"])
cap_token = os.environ["PAYBOND_CAPABILITY"]
payee_seed = bytes.fromhex(os.environ["PAYBOND_PAYEE_SEED_HEX"]) # 32 bytes
binding = PaybondCapabilityBinding(
harbor=paybond.harbor,
intent_id=intent_id,
capability_token=cap_token,
)
verified = await binding.harbor.verify_capability(
intent_id=binding.intent_id,
token=binding.capability_token,
operation="payments.capture",
requested_spend_cents=18_700,
)
if not verified.allow:
raise RuntimeError(
f"verify denied: {verified.code or 'deny'} {verified.message or ''}".strip()
)
# …run the tool work guarded by the verified capability here…
submitted_at = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
result = await paybond.intents.submit_evidence(
intent_id,
{"flight_booked": True, "price": 187, "confirmation": "PNR-4GH271"},
payee_did="did:web:example.com#booker-agent",
payee_signing_seed=payee_seed,
submitted_at_rfc3339=submitted_at,
idempotency_key=f"evidence:{intent_id}:{submitted_at}",
)
print("evidence recorded:", result["state"], result.get("predicate_passed"))
finally:
await paybond.aclose()
asyncio.run(main())Notes:
verify_capabilitymaps to HarborPOST /verify(operation idharborVerifyCapability). A200response withallow: Falseis not an HTTP error — treatcodeandmessageas the deny reason.paybond.intents.submit_evidencesigns the payee binding with canonical JSON rules and POSTs it to HarborPOST /intents/{id}/evidence(operation idharborSubmitEvidence). Reuse the sameidempotency_keyto safely retry a transient 5xx; changing the body under the same key will fail with409.- Harbor enforces tenant and intent echo on every response; the Kit raises
TenantBindingErroron mismatches so confused-deputy bugs fail closed. - Keep
payee_signing_seedin a secret manager. The value must be exactly 32 bytes and must correspond to the payee key bound on the intent; any drift will be rejected server-side.
Agent framework integrations
Paybond ships first-party hooks for the OpenAI Agents SDK and LangGraph that wrap the same PaybondCapabilityBinding shown above, so you do not have to re-implement the verify/submit loop inside tool bodies.
from paybond_kit.agents_sdk import paybond_capability_input_guardrail
from paybond_kit.langgraph_hooks import paybond_awrap_tool_call_capability
from langgraph.prebuilt import ToolNode
guard = paybond_capability_input_guardrail()
# Attach `guard` to OpenAI Agents tools and pass `context=binding` into Runner.run(...).
tool_node = ToolNode(
tools=[], # your tools
awrap_tool_call=paybond_awrap_tool_call_capability(binding),
)See Agent integrations for end-to-end examples, including the LangGraph runnable sketch shipped under examples/paybond-kit-langgraph-python/.
Principal-signed intent create
paybond.intents.create wraps the principal-side signing dance: it builds a canonical CreateIntentRequest body, signs it with the principal's 32-byte Ed25519 seed, and POSTs it to Harbor (operation id harborCreateIntent).
import asyncio
import os
from uuid import UUID, uuid4
from paybond_kit import Paybond
async def main() -> None:
principal_seed = bytes.fromhex(os.environ["PAYBOND_PRINCIPAL_SEED_HEX"]) # 32 bytes
payee_seed = bytes.fromhex(os.environ["PAYBOND_PAYEE_SEED_HEX"]) # 32 bytes
paybond = await Paybond.open(
gateway_base_url="https://gateway.example.com",
api_key=os.environ["PAYBOND_API_KEY"],
harbor_base_url="https://harbor.example.com",
)
try:
created = await paybond.intents.create(
principal_did="did:web:example.com#principal",
principal_signing_seed=principal_seed,
payee_did="did:web:example.com#booker-agent",
budget={"currency": "usd", "max_spend_usd": 200},
predicate={"version": 1, "root": {"op": "true"}},
currency="usd",
amount_cents=20_000,
evidence_schema={"type": "object"},
deadline_rfc3339="2030-12-31T23:59:59Z",
allowed_tools=["travel.planner.plan", "travel.booker.purchase"],
intent_id=uuid4(),
idempotency_key=str(uuid4()),
)
intent_id = UUID(str(created["intent_id"]))
await paybond.intents.submit_evidence(
intent_id,
{"flight_booked": True, "price": 187, "confirmation": "PNR-4GH271"},
payee_did="did:web:example.com#booker-agent",
payee_signing_seed=payee_seed,
)
finally:
await paybond.aclose()
asyncio.run(main())Managed-policy intent creation (signing_version 3 / policy_binding) is not wrapped by this helper; call paybond.harbor.create_intent with a pre-built body when you need registry-bound signing. See Harbor API — policy registry.
allowed_tools is your namespace, not Paybond's. For example, if your runtime later verifies operation="travel.booker.purchase", that exact string must appear in the intent allow-list.
Settlement confirmation
Settlement confirmation (release / refund) is an operator-tier action and is not wrapped by the Kit today. Confirm through the Operator Console or call Harbor directly: POST /intents/{id}/settlement/confirm (operation id harborConfirmSettlement). See the Harbor API reference.
Error handling
| Kit symbol | Raised when | Notes |
|---|---|---|
GatewayAuthError | harbor-access rejected the API key or omitted tenant_id / access_token. | status_code and redacted body_text are attached to the instance. |
HarborHttpError | Any Harbor 4xx/5xx after retries are exhausted. | Log status_code, url, and a redacted body_text; never log the API key, JWT, or capability token. |
TenantBindingError | Harbor echoed a different tenant or intent than requested. | Treat as severity-zero; do not retry. |
See the Error handling guide for retry/backoff guidance and logging hygiene.
Further reading
- Python SDK reference — full surface, including
Paybond.open,paybond.intents.{create, submit_evidence},paybond.harbor.verify_capability,paybond.aclose, and typed error classes. - Authentication & tenant binding.
- Evidence & artifacts and Capabilities.
- Agent integrations.
- Harbor API — the OpenAPI contract the Kit calls.