This page documents the public Python SDK. Use it when you need the exact coroutine signatures, result types, and error classes exposed by paybond-kit.
For a first paid-tool guardrail integration, start with One-command guardrails. For a guided production integration, use the Python quickstart. For the underlying HTTP contract, use the Harbor API reference and API documentation.
This SDK currently covers the Harbor application lifecycle, tenant-scoped ledger reads, protocol helpers, A2A discovery, read-only tenant-bound Signal reads, fraud review surfaces, and the packaged MCP server.
All I/O is async. The Kit does not expose a sync façade today.
Package and runtime
paybond-kit supports Python 3.11+ and uses httpx.AsyncClient for network I/O.
pip install paybond-kit
Optional extras:
paybond-kit[mcp]— installs the stdio MCP server dependency.paybond-kit[langgraph]— installs LangGraph hook dependencies.
The package also exposes:
paybond_kit.mcp_server— programmatic MCP server settings and builder helpers.paybond-kit-login— sandbox device approval CLI that writesPAYBOND_API_KEYto.env.local.paybond-mcp-server— the packaged stdio MCP server CLI.paybond-kit-init— a scaffold command for Paybond guardrail integration helpers.
Module entry point
from paybond_kit import (
Paybond,
PaybondIntents,
PaybondCapabilityBinding,
PaybondSpendGuard,
PaybondSpendDeniedError,
authorize_spend,
guard_tool,
paybond_agent_tool_spend_guard,
paybond_runtime_neutral_tool_spend_guard,
paybond_runtime_tool_call_adapter,
paybond_langgraph_tool_spend_guard,
paybond_mcp_tool_spend_guard,
ServiceAccountSignalSession,
ServiceAccountFraudSession,
GatewaySandboxGuardrailsClient,
SandboxGuardrailBootstrapResult,
SandboxGuardrailEvidenceResult,
GatewayHarborClient,
HarborClient,
GatewaySignalClient,
GatewayFraudClient,
GatewayA2AClient,
GatewayProtocolClient,
FundIntentResult,
IntentFundingResult,
SettlementRail,
VerifyCapabilityResult,
HarborHttpError,
GatewayAuthError,
A2AHttpError,
FraudHttpError,
ProtocolHttpError,
SignalHttpError,
TenantBindingError,
AgentMandateV1,
SignedAgentMandateV1,
AgentRecognitionProofV1,
ProtocolTransportBindingV1,
ProtocolAuthorizationReceiptV1,
ProtocolSettlementReceiptV1,
ImportAgentMandateV1Result,
VerifyProtocolReceiptV1Result,
sign_payee_evidence_binding,
)
MCP is exposed from a submodule:
from paybond_kit.mcp_server import (
PaybondMCPSettings,
build_mcp_server,
main as run_paybond_mcp_server,
)
High-level client
Paybond.open
@classmethod
async def Paybond.open(
cls,
*,
api_key: str, # "paybond_sk_live_…" or "paybond_sk_sandbox_…" service-account key
expected_environment: Literal["live", "sandbox"] | None = None,
max_retries: int = 3,
) -> Paybond: ...
Resolves the service-account principal at the hosted Gateway, caches the echoed tenant_id, and returns a Paybond instance bound to that tenant realm. Hosted integrations only need api_key.
Set expected_environment to fail fast when a deployment is accidentally pointed at a live key from sandbox, or a sandbox key from live.
Raises GatewayAuthError on any tenant-principal lookup or credential failure.
Tenant isolation: the returned session is bound to the Gateway-derived tenant_id. Do not reuse the instance across tenants — build one per (tenant, service account).
Paybond instance
@dataclass
class Paybond:
harbor: GatewayHarborClient # tenant-bound Gateway Harbor surface
signal: GatewaySignalClient # tenant-bound Signal reads
fraud: GatewayFraudClient # tenant-bound fraud reads and review events
a2a: GatewayA2AClient # A2A discovery reads
protocol: GatewayProtocolClient # protocol-v2 and recognition-gated helpers
guardrails: GatewaySandboxGuardrailsClient # sandbox-only first guardrail helpers
intents: PaybondIntents # ergonomic intent helpers
async def authorize_spend(
self,
*,
intent_id: UUID,
token: str,
operation: str,
requested_spend_cents: int = 0,
) -> VerifyCapabilityResult: ...
def spend_guard(self, intent_id: UUID, capability_token: str) -> PaybondSpendGuard: ...
async def aclose(self) -> None: ...
paybond.harbor— the tenant-bound Gateway-backed Harbor surface. Use it for capability verification and recognition-gated Harbor proxy calls.paybond.signal— the tenant-boundGatewaySignalClient.paybond.fraud— the tenant-boundGatewayFraudClient.paybond.a2a— theGatewayA2AClientfor A2A discovery.paybond.protocol— the tenant-boundGatewayProtocolClientfor protocol-v2 receipts, mandate import, and recognition-gated gateway mutations.paybond.guardrails— the sandbox-onlyGatewaySandboxGuardrailsClientfor first paid-tool guardrail integration.paybond.intents— seePaybondIntents.paybond.aclose()— release HTTP resources for Harbor, Signal, fraud, A2A, and protocol clients. Always await in afinallyblock orasync with contextlib.aclosing(...).
GatewaySandboxGuardrailsClient
paybond.guardrails wraps the Free Developer sandbox guardrail routes. These routes derive tenant scope from the service-account API key, reject caller-supplied tenant IDs, and use simulator settlement only.
async def bootstrap_sandbox(
*,
operation: str,
requested_spend_cents: int,
currency: str | None = None,
evidence_schema: Mapping[str, Any] | None = None,
metadata: Mapping[str, Any] | None = None,
idempotency_key: str | None = None,
) -> SandboxGuardrailBootstrapResult: ...
async def submit_sandbox_evidence(
intent_id: UUID,
payload: Mapping[str, Any] | None = None,
*,
artifacts: list[str] | None = None,
operation: str | None = None,
requested_spend_cents: int | None = None,
metadata: Mapping[str, Any] | None = None,
idempotency_key: str | None = None,
) -> SandboxGuardrailEvidenceResult: ...
Both result dataclasses include tenant_id, intent_id, operation, requested_spend_cents, and sandbox_lifecycle_status; bootstrap also returns the capability_token used with paybond.spend_guard(...).
PaybondIntents
paybond.intents wraps the principal/payee signing flows plus x402 funding. All methods derive tenant scope from the bound Harbor client; callers never pass a tenant id.
paybond.intents.create
async def create(
self,
*,
principal_did: str,
principal_signing_seed: bytes, # exactly 32 bytes
recognition_proof: Mapping[str, Any],
payee_did: str,
budget: Mapping[str, Any], # canonical JSON (e.g. {"currency": ..., "max_spend_usd": ...})
predicate: Mapping[str, Any], # predicate_dsl (version 1 and root node)
currency: str,
amount_cents: int,
evidence_schema: Mapping[str, Any], # JSON schema for evidence payloads
deadline_rfc3339: str,
allowed_tools: list[str],
settlement_rail: SettlementRail, # principal-signed rail request; destination stays server-owned
intent_id: UUID | None = None, # defaults to uuid4()
predicate_ref: str = "", # managed-policy digest when set
idempotency_key: str | None = None,
) -> dict[str, Any]: ...
Builds a principal-signed intent body via the bundled _native.build_signed_create_intent_json and POSTs it through Gateway POST /harbor/intents.
paybond.intents.create_spend_intent(**kwargs) is a spend-oriented alias for the same method. Use it in agent-facing code when the user asked for agent spend controls, delegated spend guardrails, or tool-call budget checks.
Requires the bundled native signing extension. Published wheels include it. Without it, an ImportError is raised on the first call.
Retries honor idempotency_key on transient 5xx/429. Reusing a key with a different body returns HTTP 409 from Harbor (see docs/api/harbor-idempotency-openapi.yaml).
allowed_tools is a caller-defined allow-list of delegated operation names such as travel.planner.plan or crm.contact.enrich. Paybond does not define these strings; Harbor only checks that later verify calls use a matching value.
settlement_rail is required and included in the principal signature. It requests one allowed rail for the new intent. Harbor validates it against the tenant's canonical settlement config, resolves the destination server-side, and snapshots the effective routing onto the intent.
paybond.intents.fund
async def fund(
self,
intent_id: UUID,
*,
recognition_proof: Mapping[str, Any],
payment_signature: str | None = None,
idempotency_key: str | None = None,
) -> FundIntentResult: ...
Advances Gateway POST /harbor/intents/{intent_id}/fund for x402_usdc_base intents. Returns structured 402 challenges (payment_required), 202 pending states, and 200 funded results without forcing callers to catch those statuses as exceptions.
paybond.intents.submit_evidence
async def submit_evidence(
self,
intent_id: UUID,
payload: Mapping[str, Any], # evidence JSON matching the intent's schema
*,
payee_did: str,
payee_signing_seed: bytes, # exactly 32 bytes
recognition_proof: Mapping[str, Any],
artifacts_blake3_hex: list[str] | None = None,
submitted_at_rfc3339: str | None = None, # defaults to now (UTC)
idempotency_key: str | None = None,
) -> dict[str, Any]: ...
Signs the payee binding via sign_payee_evidence_binding and POSTs it through Gateway POST /harbor/intents/{intent_id}/evidence.
Gateway-backed Harbor mutations require a replay-safe recognition proof bound to the exact method, path, purpose, tenant, and request body.
The high-level helper defaults artifacts_blake3_hex to [] and submitted_at_rfc3339 to the current UTC second. The low-level sign_payee_evidence_binding helper still requires concrete values because they are part of the signed bytes.
The response includes state, tenant, and an optional predicate_passed; a False here is not an error — the predicate evaluation failed and the intent moved to evidence_submitted with the recorded failure.
Verify (Gateway-enriched)
The Kit exposes capability verification on the underlying Harbor client rather than on paybond.intents. On hosted Gateway sessions, POST /verify runs Harbor capability validation first, then applies tenant spend policy before returning the final allow/deny outcome.
await paybond.harbor.verify_capability(
intent_id=intent_id,
token=capability_token,
operation="travel.book_hotel",
requested_spend_cents=18_700,
vendor_id="vendor_acme",
task_id="task_42",
workflow_id="wf_travel",
tool_call_id="call_1",
tool_name="travel.book_hotel",
currency="usd",
agent_subject="agent:ops-bot",
approval_token=approval_token, # after operator approval
idempotency_key="verify-key-1",
)
Pass vendor, task, workflow, tool, and agent metadata when tenant spend policy evaluates rolling caps, anomaly rules, or approval thresholds against those scopes. After an operator approves a held request in the console, retry with the same operation, amount, and metadata plus approval_token.
Returns a VerifyCapabilityResult with allow=False when Harbor or Gateway denies — that is not raised. When Gateway policy requires human approval first, inspect result.approval_required and usually decision_id / approval_request_id; handle that separately from a hard policy denial (see PaybondSpendGuard).
Confirm settlement
Settlement confirmation is an operator-tier action and is not wrapped by paybond.intents. For protocol-driven flows, use the recognition-gated paybond.protocol.confirm_harbor_settlement helper:
await paybond.protocol.confirm_harbor_settlement(
str(intent_id),
body={}, # Harbor derives release/refund from the recorded predicate result.
recognition_proof=recognition_proof,
idempotency_key=settlement_idempotency_key,
)
The helper calls the Gateway /harbor/intents/{intent_id}/settlement/confirm route with x-tenant-id and x-paybond-agent-recognition-proof. Gateway still enforces the protocol recognition proof, tenant scope, and operator/administrative policy. Harbor does not let this route choose release or refund; it confirms the settlement action implied by the evidence evaluation already stored on the intent. Prefer the Operator Console for human-reviewed settlement.
Gateway Harbor client
class GatewayHarborClient:
def __init__(
self,
gateway_base_url: str,
tenant_id: str,
*,
static_gateway_bearer_token: str,
max_retries: int = 3,
) -> None: ...
@property
def tenant_id(self) -> str: ...
async def verify_capability(...) -> VerifyCapabilityResult: ...
async def complete_spend_decision(
self,
*,
decision_id: str,
outcome: Literal["consumed", "released"],
) -> None: ...
async def create_intent(..., recognition_proof: Mapping[str, Any], ...) -> dict[str, Any]: ...
async def aclose(self) -> None: ...
Paybond.open(...) constructs this for hosted integrations. Build it directly only when you already have a trusted tenant id from Gateway principal introspection.
HarborClient
Tenant-bound async HTTP client. Sends x-tenant-id on every request; verifies Harbor echoes the same tenant and the requested intent_id on POST /verify (confused-deputy hardening — raises TenantBindingError on mismatch).
class HarborClient:
def __init__(
self,
harbor_base: str,
tenant_id: str,
*,
harbor_bearer_supplier: Callable[[], Awaitable[str | None]] | None = None,
static_harbor_bearer_token: str | None = None,
max_retries: int = 3,
request_timeout_sec: float = 30.0,
) -> None: ...
@property
def tenant_id(self) -> str: ...
async def verify_capability(
self,
*,
intent_id: UUID,
token: str,
operation: str,
requested_spend_cents: int = 0,
) -> VerifyCapabilityResult: ... # harborVerifyCapability
async def create_intent(
self,
body: dict[str, Any],
*,
idempotency_key: str | None = None,
) -> dict[str, Any]: ... # harborCreateIntent
async def fund_intent(
self,
intent_id: UUID,
*,
payment_signature: str | None = None,
idempotency_key: str | None = None,
) -> FundIntentResult: ... # harborFundIntent
async def submit_evidence(
self,
intent_id: UUID,
evidence_body: dict[str, Any],
*,
idempotency_key: str | None = None,
) -> dict[str, Any]: ... # harborSubmitEvidence
async def get_ledger_tip(self) -> dict[str, Any]: ...
async def get_ledger_authority(self) -> dict[str, Any]: ...
async def get_ledger_events(
self,
*,
after_seq: int = 0,
limit: int = 64,
) -> dict[str, Any]: ...
async def get_ledger_merkle_latest(self) -> dict[str, Any]: ...
async def aclose(self) -> None: ...
Retries 429 / 500 / 502 / 503 / 504 with exponential backoff and jitter, honoring a bounded Retry-After. All other 4xx/5xx raise HarborHttpError.
FundIntentResult
Structured return type for paybond.intents.fund(...) and harbor.fund_intent(...).
status_code:402,202, or200payment_required: x402 challenge header whenstatus_code == 402payment_response: optional Coinbase receipt header on202or200intent_id,tenant,statesettlement_rail,currency,amount_centsfunded: whether Harbor has transitioned the intent into the funded statefunding: optionalIntentFundingResultcapability_token: present when Harbor already minted the funded capability
IntentFundingResult
Normalized Harbor funding payload fields:
settlement_railharbor_fund_endpointstatuspayment_session_idpayment_urlstripe_payment_intent_id,client_secret,stripe_connect_destinationstripe_customer_id,latest_charge_id,payment_method_id,mandate_idfinancial_connections_account_id,bank_last4,bank_fingerprint,bank_nameasset,networkauthorization_id,capture_id,void_id,transfer_id,refund_idexpected_debit_date,payment_reference,refund_reference,refund_reference_statussource_address,target_addressauthorization_expires_at,capture_expires_at,refund_expires_atonchain_transaction_hashes
SettlementRail
SettlementRail = Literal["stripe_connect", "stripe_ach_debit", "x402_usdc_base"]
Public type alias for the only supported client-requestable settlement rails. The SDK does not expose any client-side destination fields; Harbor resolves destinations from tenant-owned settlement config.
Ledger reads
The SDK includes protected tenant-scoped provenance reads on the low-level direct HarborClient. Hosted Paybond.open(...) integrations should prefer Gateway's public compliance surface, verifier packs, verification results, and audit exports instead of a raw ledger event stream.
get_ledger_tip()→GET /ledger/v1/tipget_ledger_authority()→GET /ledger/v1/authorityget_ledger_events(after_seq=..., limit=...)→GET /ledger/v1/eventsget_ledger_merkle_latest()→GET /ledger/v1/merkle/latest
Each method validates the echoed tenant_id against the bound client tenant and fails closed on mismatch.
Signal reads
The Python SDK ships a tenant-bound gateway Signal client for the supported read-only V1.0.1 surfaces.
GatewaySignalClient
class GatewaySignalClient:
def __init__(
self,
gateway_base_url: str,
tenant_id: str,
*,
static_gateway_bearer_token: str,
max_retries: int = 3,
request_timeout_sec: float = 30.0,
) -> None: ...
@property
def tenant_id(self) -> str: ...
async def get_reputation_receipt(
self,
operator_did: str,
*,
score_version: str | None = None,
) -> SignalReceiptEnvelope | None: ...
async def get_portfolio_summary(
self,
*,
score_version: str | None = None,
) -> SignalPortfolioSummary: ...
async def get_signed_portfolio_artifact(
self,
*,
score_version: str | None = None,
) -> SignalSignedPortfolioArtifact: ...
async def get_operator_explanation(
self,
operator_did: str,
*,
score_version: str | None = None,
) -> dict[str, Any] | None: ...
async def get_operator_review_status(
self,
operator_did: str,
*,
score_version: str | None = None,
) -> dict[str, Any] | None: ...
async def aclose(self) -> None: ...
This client validates the echoed tenant_id and operator_did on every response. 404 remains a None result for receipt, explanation, and review-status lookups; other non-success statuses raise SignalHttpError. get_signed_portfolio_artifact() returns the tenant-scoped signed portfolio export that partners and verifiers can carry across systems without turning Signal into a public leaderboard surface.
ServiceAccountSignalSession
@dataclass
class ServiceAccountSignalSession:
signal: GatewaySignalClient
@classmethod
async def open(
cls,
*,
gateway_base_url: str,
api_key: str,
principal_path: str = "/v1/auth/principal",
expected_environment: Literal["live", "sandbox"] | None = None,
max_retries: int = 3,
) -> ServiceAccountSignalSession: ...
async def aclose(self) -> None: ...
This is the easiest tenant-safe path for partner developers who only need Signal reads. The session resolves the tenant from GET /v1/auth/principal using the same service-account credential instead of relying on ad hoc tenant headers.
Fraud review and metrics
GatewayFraudClient
class GatewayFraudClient:
def __init__(
self,
gateway_base_url: str,
tenant_id: str,
*,
static_gateway_bearer_token: str,
max_retries: int = 3,
request_timeout_sec: float = 30.0,
) -> None: ...
@property
def tenant_id(self) -> str: ...
async def get_fraud_assessment(
self,
operator_did: str,
*,
score_version: str | None = None,
) -> SignalFraudAssessmentResponse | None: ...
async def list_fraud_review_queue(
self,
*,
state: str | None = None,
severity: str | None = None,
limit: int | None = None,
score_version: str | None = None,
) -> SignalFraudReviewQueueResponse: ...
async def get_fraud_metrics(
self,
*,
window: str | None = None,
score_version: str | None = None,
) -> SignalFraudMetricsResponse: ...
async def get_fraud_release_gate_config(
self,
*,
score_version: str | None = None,
) -> SignalFraudReleaseGateConfigResponse: ...
async def set_fraud_release_gate_mode(
self,
mode: str,
) -> SignalFraudReleaseGateConfigResponse: ...
async def record_fraud_review_event(
self,
operator_did: str,
event: SignalFraudReviewEventInput,
*,
score_version: str | None = None,
) -> SignalFraudReviewEventResponse: ...
async def aclose(self) -> None: ...
The fraud client validates echoed tenant_id and, for operator-scoped methods, operator_did. SignalFraudMetricsResponse includes rolling-window backtest and monitoring fields such as false_positive_rate_bps, labeled_coverage_bps, critical_signal_hold_candidate_count, stale_label_gap_seconds, stale_signal_family_label_gap_count, and backtest_summary. SignalFraudSignal preserves the fraud_signal_version=1.0.7 additive metadata fields signal_source, first_seen_at, last_seen_at, evidence_binding_strength, provider_event_refs, and intent_refs when Gateway returns them. set_fraud_release_gate_mode() accepts only review_only or critical_hold; Gateway still enforces tenant-admin access and keeps review_only as the default. record_fraud_review_event() only accepts review workflow/outcome event types, does not expose settlement or money-movement events, and can pass optional signal-level feedback context through signalCode/signal_code, intentId/intent_id, and providerEventId/provider_event_id.
ServiceAccountFraudSession
@dataclass
class ServiceAccountFraudSession:
fraud: GatewayFraudClient
@classmethod
async def open(
cls,
*,
gateway_base_url: str,
api_key: str,
principal_path: str = "/v1/auth/principal",
expected_environment: Literal["live", "sandbox"] | None = None,
max_retries: int = 3,
) -> ServiceAccountFraudSession: ...
async def aclose(self) -> None: ...
Paybond.open(...) also exposes gateway clients as paybond.signal, paybond.fraud, paybond.a2a, and paybond.protocol.
A2A discovery
GatewayA2AClient
class GatewayA2AClient:
def __init__(
self,
gateway_base_url: str,
*,
static_gateway_bearer_token: str | None = None,
max_retries: int = 3,
request_timeout_sec: float = 30.0,
) -> None: ...
async def get_agent_card(self) -> dict[str, Any]: ...
async def get_task_contracts(self) -> dict[str, Any]: ...
async def get_task_contract(self, contract_id: str) -> dict[str, Any]: ...
async def aclose(self) -> None: ...
paybond.a2a is created automatically by Paybond.open and carries the same service-account bearer token when available. Use it for:
GET /.well-known/agent-card.jsonGET /protocol/v2/a2a/task-contractsGET /protocol/v2/a2a/task-contracts/{contract_id}
Non-success statuses raise A2AHttpError. See Agent integrations and V2 protocol trust.
Protocol v2 and recognition-gated gateway mutations
GatewayProtocolClient
class GatewayProtocolClient:
def __init__(
self,
gateway_base_url: str,
tenant_id: str,
*,
static_gateway_bearer_token: str | None = None,
max_retries: int = 3,
request_timeout_sec: float = 30.0,
) -> None: ...
@property
def tenant_id(self) -> str: ...
async def import_agent_mandate_v1(
self,
*,
signed_mandate: SignedAgentMandateV1,
intent_id: str,
recognition_proof: AgentRecognitionProofV1 | dict[str, Any],
transport_binding: ProtocolTransportBindingV1 | None = None,
) -> ImportAgentMandateV1Result: ...
async def get_settlement_receipt_v1(
self,
receipt_id: str,
) -> ProtocolSettlementReceiptV1: ...
async def verify_protocol_receipt_v1(
self,
receipt: ProtocolAuthorizationReceiptV1 | ProtocolSettlementReceiptV1 | dict[str, Any],
) -> VerifyProtocolReceiptV1Result: ...
async def create_harbor_intent(
self,
*,
body: dict[str, Any],
recognition_proof: AgentRecognitionProofV1 | dict[str, Any],
idempotency_key: str | None = None,
) -> dict[str, Any]: ...
async def fund_harbor_intent(
self,
intent_id: str,
*,
recognition_proof: AgentRecognitionProofV1 | dict[str, Any],
payment_signature: str | None = None,
idempotency_key: str | None = None,
) -> dict[str, Any]: ...
async def submit_harbor_evidence(
self,
intent_id: str,
*,
body: dict[str, Any],
recognition_proof: AgentRecognitionProofV1 | dict[str, Any],
idempotency_key: str | None = None,
) -> dict[str, Any]: ...
async def confirm_harbor_settlement(
self,
intent_id: str,
*,
body: dict[str, Any], # usually {}; included in the recognition envelope
recognition_proof: AgentRecognitionProofV1 | dict[str, Any],
idempotency_key: str | None = None,
) -> dict[str, Any]: ...
async def aclose(self) -> None: ...
paybond.protocol is created automatically by Paybond.open. It sends x-tenant-id, adds Authorization when a gateway bearer token is configured, and base64url-encodes the recognition proof into x-paybond-agent-recognition-proof for recognition-gated Harbor mutation helpers.
Paybond is AP2-aligned at the protocol boundary. On mandate import, transport_binding["source_protocol"] is optional and Gateway defaults it to ap2 for imported external mandates and exported protocol receipts.
Gateway HTTP failures raise ProtocolHttpError with parsed error_code / error_message when Gateway returns a JSON error envelope. Tenant, intent, and receipt echo mismatches raise RuntimeError and should be treated as critical tenant-isolation failures.
Core protocol types
The SDK exports the protocol shapes used by GatewayProtocolClient:
AgentMandateV1/SignedAgentMandateV1AgentRecognitionProofV1ProtocolTransportBindingV1ProtocolAuthorizationReceiptV1ProtocolSettlementReceiptV1ImportAgentMandateV1ResultVerifyProtocolReceiptV1Result
MCP server submodule
@dataclass(frozen=True)
class PaybondMCPSettings:
api_key: str
gateway_base_url: str = "https://api.paybond.ai"
principal_path: str = "/v1/auth/principal"
max_retries: int = 3
@classmethod
def from_env(cls, env: Mapping[str, str] | None = None) -> PaybondMCPSettings: ...
def build_mcp_server(settings: PaybondMCPSettings | None = None) -> Any: ...
def run_paybond_mcp_server(argv: list[str] | None = None) -> int: ...
The packaged CLI is usually simpler:
paybond-mcp-server
Required environment is PAYBOND_API_KEY. See the MCP server guide for the supported tool list and host configuration.
PaybondCapabilityBinding
Most application code should use paybond.spend_guard(intent_id, capability_token) instead. This
binding remains available for framework adapters that require a run-context object, such as the
LangGraph hook.
@dataclass
class PaybondCapabilityBinding:
harbor: HarborClient
intent_id: UUID
capability_token: str
async def verify_spend_capability(
self,
*,
operation: str,
requested_spend_cents: int = 0,
) -> VerifyCapabilityResult: ...
async def authorize_spend(
self,
*,
operation: str,
requested_spend_cents: int = 0,
) -> VerifyCapabilityResult: ...
Binds (harbor, intent_id, capability_token) so agent framework hooks have a single tenant-safe handle to verify against. Use paybond.spend_guard(...) for the runtime-neutral adapter; use this binding when a framework hook expects a context object. See Agent integrations.
PaybondSpendGuard
guard = paybond.spend_guard(intent_id, capability_token)
guarded_tool = guard.guard_tool(
operation="travel.book_hotel",
requested_spend_cents=20_000,
vendor_id="vendor_acme",
tool_name="travel.book_hotel",
agent_subject="agent:ops-bot",
handler=book_hotel,
)
PaybondSpendGuard is a discoverable wrapper around Gateway-enriched capability verification. It calls verify_capability before the handler runs:
- raises
PaybondSpendDeniedErroron hard denials (allow=Falsewithout approval required) - raises
PaybondSpendApprovalRequiredErrorwhenresult.approval_requiredis true - after a successful authorization,
guard_toolruns the handler and then callscomplete_spend_decisionwith outcomeconsumedon success orreleasedwhen the handler raises (best-effort release)
If you call authorize_spend or assert_spend_authorized directly instead of guard_tool, call complete_spend_authorization yourself when the guarded work finishes or aborts.
Standalone helpers are also exported. They accept a source with
harbor, intent_id, and capability_token attributes, including
PaybondCapabilityBinding:
authorize_spend(source, operation=..., requested_spend_cents=..., vendor_id=..., approval_token=..., ...)guard_tool(source, operation=..., requested_spend_cents=..., handler=...)paybond_agent_tool_spend_guard(source, operation=..., handler=...)paybond_runtime_neutral_tool_spend_guard(source, operation=..., handler=...)paybond_langgraph_tool_spend_guard(source, operation=..., handler=...)paybond_mcp_tool_spend_guard(source, operation=..., handler=...)
Optional verify metadata on authorize_spend, verify_spend_capability, and guard_tool: vendor_id, task_id, workflow_id, tool_call_id, tool_name, currency, agent_subject, approval_token, idempotency_key.
For agent SDKs or custom runtimes that hand your application a tool-call object and an executor,
use paybond_runtime_tool_call_adapter(...):
run_tool = paybond_runtime_tool_call_adapter(
guard,
operation=lambda call: call["name"],
requested_spend_cents=lambda call: int(call.get("spend_cents", 0)),
execute=execute_tool_call,
on_deny=lambda result, _: {
"status": "blocked",
"reason": result.message or result.code,
},
)
Scaffolding
Sandbox credential setup:
paybond-kit-login
The login command supports --env-file, --gateway, --no-open, and --force. It writes .env.local with mode 0600, adds the default .env.local target to .gitignore when needed, refuses custom unignored env files inside git repos, and prints only masked key identity plus the target sandbox tenant.
paybond-kit-init \
--preset paid-tool-guard \
--framework provider-agnostic \
--out paybond_paid_tool_guard.py
Supported framework labels are generic, provider-agnostic, openai, claude, anthropic, gemini, google-ai, langgraph, and mcp. The scaffold writes reusable guardrail helpers and expects your application to provide the paid-tool handler; it does not manage signing keys or payment wallets.
Result types
VerifyCapabilityResult
Gateway-enriched verify outcome. Harbor-only deployments populate the core fields; hosted Gateway sessions may also return spend-policy metadata.
@dataclass(frozen=True)
class VerifyCapabilityResult:
allow: bool
audit_id: UUID
tenant: str
intent_id: UUID
code: str | None
message: str | None
decision_id: UUID | None = None
approval_request_id: UUID | None = None
policy_version: int | None = None
reason_codes: tuple[str, ...] = ()
spend_scope: dict[str, str] | None = None
remaining_cents: int | None = None
retry_after: int | None = None
@property
def approval_required(self) -> bool: ...
| Field | Meaning |
|---|---|
allow | Final authorization decision after Harbor capability checks and Gateway spend policy. |
approval_required | Policy hold — not a hard deny. Surface to operators and retry with approval_token after approval. |
decision_id | Immutable authorization decision id; used to finalize scope reservations after tool execution. |
approval_request_id | Pending approval queue item when quorum is incomplete. |
reason_codes | Machine-readable policy outcomes (for example approval_threshold_exceeded, daily_tenant_cap_exceeded). |
spend_scope / remaining_cents | Scope that governed the check and optional remaining budget headroom. |
PaybondSpendDeniedError / PaybondSpendApprovalRequiredError
PaybondSpendGuard.assert_spend_authorized and guard_tool raise PaybondSpendDeniedError when allow is false and approval is not required. They raise PaybondSpendApprovalRequiredError when approval_required is true. Both exceptions expose a result: VerifyCapabilityResult attribute for logging and operator UX.
Evidence response
paybond.intents.submit_evidence returns a plain dict[str, Any] with at least the following keys:
| Key | Type | Meaning |
|---|---|---|
intent_id | str (UUID) | Harbor-echoed intent id; compared against the request. |
tenant | str | Harbor-echoed tenant realm. |
state | str | New intent state (for example "evidence_submitted"). |
predicate_passed | bool (optional) | True if the predicate VM accepted the evidence. |
ImportAgentMandateV1Result
class ImportAgentMandateV1Result(TypedDict):
valid: bool
intent_id: str
mandate_digest_sha256_hex: str
mandate: AgentMandateV1
authorization_receipt: ProtocolAuthorizationReceiptV1
Returned by GatewayProtocolClient.import_agent_mandate_v1. The client verifies the echoed intent_id and mandate tenant before returning.
VerifyProtocolReceiptV1Result
class VerifyProtocolReceiptV1Result(TypedDict):
valid: bool
kind: str
receipt_id: str
tenant_id: str
receipt: dict[str, Any]
Returned by GatewayProtocolClient.verify_protocol_receipt_v1 for authorization and settlement receipt verification.
Canonical signing helpers
sign_payee_evidence_binding
def sign_payee_evidence_binding(
*,
tenant_id: str,
intent_id: UUID,
payee_did: str,
payload: dict[str, Any],
artifacts_blake3_hex: list[str],
submitted_at_rfc3339: str,
payee_signing_seed: bytes,
) -> dict[str, Any]: ...
Builds the signed evidence wire body used by operation harborSubmitEvidence. Exported for advanced callers that pre-build evidence bodies outside of paybond.intents.submit_evidence.
Principal-intent signing is performed in the native extension via _native.build_signed_create_intent_json. Canonical JSON normalization, BLAKE3 digests, and Ed25519 signing match Paybond's canonical signing rules byte-for-byte.
Typed errors
All error classes extend RuntimeError and attach structured diagnostics. Never log raw tokens or API keys; the Kit performs no redaction itself.
HarborHttpError
class HarborHttpError(RuntimeError):
status_code: int
url: str
body_text: str # raw Harbor response body (may be JSON)
Raised by HarborClient when Harbor returns any non-success status after retries. Look for the machine-readable code field inside body_text — contracts are in the Error envelope reference.
A2AHttpError
class A2AHttpError(RuntimeError):
status_code: int
url: str
body_text: str
Raised by GatewayA2AClient when A2A discovery routes return non-success statuses after retries.
ProtocolHttpError
class ProtocolHttpError(RuntimeError):
status_code: int
url: str
body_text: str # raw gateway response body (may be JSON)
error_code: str | None # parsed gateway "error" field when present
error_message: str | None # parsed gateway "message" field when present
Raised by GatewayProtocolClient and paybond.protocol for gateway-backed protocol/v2/* calls plus recognition-gated gateway /harbor/* mutation helpers. The Kit parses JSON gateway error envelopes so callers can branch on explicit codes such as unregistered_key, tenant_mismatched_key, revoked_key, mandate_agent_key_mismatch, and protocol_binding_mismatch without reparsing body_text.
GatewayAuthError
class GatewayAuthError(RuntimeError):
status_code: int | None
body_text: str | None
Raised when Gateway rejects the API key, returns malformed JSON, or omits tenant_id.
FraudHttpError
class FraudHttpError(RuntimeError):
status_code: int
url: str
body_text: str
Raised by GatewayFraudClient when gateway fraud routes return a non-success status after retries, excluding the 404 -> None read contract on fraud assessment lookups.
SignalHttpError
class SignalHttpError(RuntimeError):
status_code: int
url: str
body_text: str
Raised by GatewaySignalClient when gateway Signal routes return a non-success status after retries, excluding the 404 -> None read contract on receipt, explanation, and review-status lookups.
TenantBindingError
class TenantBindingError(RuntimeError): ...
Raised when Harbor echoes a different tenant or intent than the Kit requested on protected Harbor calls such as POST /verify, POST /intents/{id}/fund, or ledger reads. Signal, fraud, and protocol clients raise RuntimeError for tenant, operator, mandate, or receipt echo mismatches. Treat all of these as critical tenant-isolation failures and do not retry.
Retries, idempotency, and logging
- Harbor and protocol state-changing helpers accept
idempotency_key. The same key with the same body is safe to retry on transient failures; the same key with a different body returns409. - Log
tenant_id,intent_id,status_code,url, correlation id (when present), and a redactedbody_text. Never log the API key, Harbor JWT, or capability token. - Backoff is exponential with jitter, bounded at ~5s per attempt;
Retry-Afteris honored up to 30s.