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.
Network I/O is async by default. For notebooks and short read-only scripts, use the optional paybond_kit.sync module — it does not add mutation wrappers for intent create, evidence submit, or agent middleware bind.
Package and runtime
paybond-kit supports Python 3.11+ and uses httpx.AsyncClient for network I/O.
pip install paybond-kit
Optional extras (quote on zsh, e.g. "paybond-kit[langgraph]"):
paybond-kit[mcp]— installs the stdio MCP server dependency (paybond-mcp-server) and enablespaybond agent demo mcp smoke.paybond-kit[langgraph]— installs LangGraph hook dependencies and enablespaybond agent demo langgraph smoke.paybond-kit[claude-agents]— installs the Claude Agents SDK and enablespaybond agent demo claude-agents smoke.
With pipx, the CLI executable is paybond (not paybond-kit): pipx install 'paybond-kit[langgraph]', or pipx inject paybond-kit langgraph langchain-core when base paybond-kit is already installed; one-shot runs use pipx run --spec 'paybond-kit[langgraph]' paybond …. See Python quickstart — pipx.
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, PaybondAuditExports, PaybondAudit, 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 gateway_base_url: str = "https://api.paybond.ai", principal_path: str = "/v1/auth/principal", 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; the default Gateway origin is https://api.paybond.ai. Pass gateway_base_url to override the origin. Scaffold helpers such as open_paybond_from_env() and the MCP server read PAYBOND_GATEWAY_URL / PAYBOND_GATEWAY_BASE_URL from the environment. See Endpoints & environments.
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 audit: PaybondAudit # compliance audit export 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.audit.exports— seePaybondAuditExports.paybond.aclose()— release HTTP resources for Harbor, Signal, fraud, A2A, protocol, and audit export 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. The key is 1–256 characters and is scoped per authenticated tenant and HTTP operation, so reusing it with the same body replays the original response and reusing it with a different body returns HTTP 409 from Harbor. See Idempotency for the shared contract and the Harbor API reference for the routes that honor it.
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, payment_authorization: str | None = None, idempotency_key: str | None = None, ) -> FundIntentResult: ...
Advances Gateway POST /harbor/intents/{intent_id}/fund. Returns structured 402 challenges, 202 pending states, and 200 funded results without forcing callers to catch those statuses as exceptions.
x402_usdc_base: Harbor returnspayment_requiredon402; retry withpayment_signature.stripe_mpp: Harbor returnsWWW-Authenticate: Payment …on402; retry withpayment_authorization(Kit sendsx-paybond-payment-authorization: Payment …soAuthorization: Bearerstays free).
paybond.intents.fund_with_x402
async def fund_with_x402( self, intent_id: UUID, *, recognition_proof: Mapping[str, Any], sign_payment: Callable[[str], Awaitable[str]], issue_recognition_proof: Callable[ [FundRequestEnvelope], Awaitable[Mapping[str, Any]] ], poll_options: X402FundPollOptions | None = None, idempotency_key: str | None = None, ) -> FundIntentResult: ...
Orchestrates the x402 fund handshake: signs 402 payment_required challenges, retries with payment-signature, and polls 202 until Harbor returns a capability_token. Wallet keys stay app-owned via injectable sign_payment and issue_recognition_proof.
For local contract testing without a remote sandbox API, use paybond dev up (WireMock scenario mappings 11–13) or paybond dev --offline (in-process x402 fund state machine). See Stablecoin funding.
paybond.intents.fund_with_mpp_charge
async def fund_with_mpp_charge( self, intent_id: UUID, *, recognition_proof: Mapping[str, Any], create_payment_credential: Callable[[str], Awaitable[str]], issue_recognition_proof: Callable[ [FundRequestEnvelope], Awaitable[Mapping[str, Any]] ], poll_options: MppFundPollOptions | None = None, idempotency_key: str | None = None, ) -> FundIntentResult: ...
Orchestrates one-shot Stripe MPP charge funding on stripe_mpp intents: selects the intent="charge" / method="stripe" WWW-Authenticate: Payment … challenge, calls your app-owned create_payment_credential, retries with payment_authorization (x-paybond-payment-authorization through Gateway), and polls when Harbor leaves funding pending until capability_token is issued (funding.status = charge_succeeded). MPP wallet and SPT secrets stay app-owned — Kit never stores signing material. See Harbor MPP handshake.
paybond.intents.fund_with_mpp_session
async def fund_with_mpp_session( self, intent_id: UUID, *, recognition_proof: Mapping[str, Any], create_payment_credential: Callable[[str], Awaitable[str]], issue_recognition_proof: Callable[ [FundRequestEnvelope], Awaitable[Mapping[str, Any]] ], poll_options: MppFundPollOptions | None = None, idempotency_key: str | None = None, ) -> FundIntentResult: ...
Same orchestration for Tempo MPP session funding: selects the intent="session" / method="tempo" Payment Auth challenge, verifies the open-session credential, and completes when funding.status = deposit_confirmed. Live deployments may return 202 with session_open_pending while the TIP-1034 deposit confirms on-chain; helpers poll until funded. See MPP funding and Harbor MPP handshake.
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 intent 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.
paybond.intents.confirm_settlement
async def confirm_settlement( self, intent_id: UUID | str, *, recognition_proof: Mapping[str, Any], body: Mapping[str, Any] | None = None, idempotency_key: str | None = None, ) -> dict[str, Any]: ...
Confirms settlement through Gateway POST /harbor/intents/{intent_id}/settlement/confirm. 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.
For protocol/A2A automation or MCP tooling, paybond.protocol.confirm_harbor_settlement and paybond_confirm_settlement remain supported.
PaybondAuditExports
paybond.audit.exports wraps Gateway compliance audit export jobs (POST and GET /v1/compliance/audit-exports). Tenant scope comes from the service-account API key — callers never pass a tenant id. Local bundle verification reuses the same Ed25519 manifest checks as paybond audit exports verify.
async def create( self, *, filter: AuditExportCreateFilter | Mapping[str, Any], disclosure_tier: str = "standard", retention_hours: int | None = None, # gateway default 168, max 720 ) -> AuditExportJobGetResponse: ... async def list( self, *, limit: int | None = None, # default 50, max 200 cursor: str | None = None, ) -> AuditExportListPage: ... async def get( self, job_id: str, *, issue_download: bool = False, # mint download_token when the job is ready ) -> AuditExportJobGetResponse: ... async def delete(self, job_id: str) -> dict[str, Any]: ... async def verify( self, manifest_or_path: Mapping[str, Any] | str, *, cwd: str | Path = ".", ) -> dict[str, Any]: ...
list and get are also exposed as readonly MCP tools (paybond_list_audit_exports, paybond_get_audit_export) when the host uses --tool-policy readonly. Local bundle verification is SDK/CLI only — MCP hosts cannot verify downloaded ZIP paths.
CLI paybond audit exports list|get|delete|verify delegates to this SDK surface. Export job creation (create) is SDK-only — there is no paybond audit exports create subcommand. Operator Console remains a primary UI for requesting bundles; see Ledger and provenance.
created = await paybond.audit.exports.create( filter={ "time_start": "2026-05-01T00:00:00Z", "time_end": "2026-05-07T23:59:59Z", }, disclosure_tier="standard", ) ready = await paybond.audit.exports.get(created.job.id, issue_download=True)
Optional sync façade (read-only)
paybond_kit.sync is an optional import for notebooks, REPL sessions, and short scripts that should not manage asyncio themselves. Wrappers call the same async clients via asyncio.run (or a worker thread when a notebook host already has a running loop). There is no second HTTP stack.
Do not use the sync façade for mutation paths — intent create, evidence submit, spend authorization, and agent middleware bind remain async-only.
from paybond_kit.sync import PaybondSync with PaybondSync.open(api_key="paybond_sk_...") as pb: jobs = pb.audit_exports_list(limit=20) receipt = pb.get_reputation_receipt("did:example:operator") portfolio = pb.get_signed_portfolio_artifact()
class PaybondSync: @classmethod def open( cls, *, api_key: str, gateway_base_url: str = DEFAULT_PAYBOND_GATEWAY_BASE_URL, principal_path: str = "/v1/auth/principal", expected_environment: PaybondEnvironment | None = None, max_retries: int = 3, ) -> PaybondSync: ... def close(self) -> None: ... @property def paybond(self) -> Paybond: ... def audit_exports_list( self, *, limit: int | None = None, cursor: str | None = None, ) -> AuditExportListPage: ... def audit_exports_get( self, job_id: str, *, issue_download: bool = False, ) -> AuditExportJobGetResponse: ... def audit_exports_verify( self, manifest_or_path: Mapping[str, Any] | str, *, cwd: str | Path = ".", ) -> dict[str, Any]: ... def audit_verify_manifest(self, manifest: Mapping[str, Any]) -> bool: ... def get_reputation_receipt( self, operator_did: str, *, score_version: str | None = None, ) -> SignalReceiptEnvelope | None: ... def get_portfolio_summary( self, *, score_version: str | None = None, ) -> SignalPortfolioSummary: ... def get_signed_portfolio_artifact( self, *, score_version: str | None = None, ) -> SignalSignedPortfolioArtifact: ...
Wrap an existing async session with PaybondSync(paybond) when you already opened Paybond in an async context.
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).
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 confirm_settlement( self, intent_id: UUID, body: dict[str, Any], *, recognition_proof: Mapping[str, Any], idempotency_key: str | None = None, ) -> 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 confirm_settlement( self, intent_id: UUID, body: dict[str, Any] | None = None, *, idempotency_key: str | None = None, ) -> dict[str, Any]: ... # harborConfirmSettlement 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 on202or200www_authenticate: Payment Auth challenges fromWWW-Authenticatewhenstatus_code == 402(MPP charge/session)payment_receipt: Payment Auth receipt fromPayment-Receiptwhen Harbor confirms payment progresscache_control: echoedCache-Controlfrom Harborintent_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_endpointstatus- MPP (Payment Auth):
intent(charge|session),method(stripe|tempo),challenge_id - MPP settlement:
settlement_asset,settlement_network,deposit_amount_base_units - MPP Tempo session:
channel_id,session_protocol(e.g.v2),accepted_cumulative_base_units,pending_cumulative_base_units,descriptor_hash,last_voucher_at,last_settle_tx_hash,channel_status payment_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,network(x402)authorization_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", "stripe_mpp", "adyen_manual_capture", "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.
Stripe MPP rail: stripe_mpp (Stripe Machine Payments Protocol) is a valid SettlementRail literal for sandbox rehearsal and flagged production paths. Use paybond.intents.fund_with_mpp_charge for one-shot charge funding or paybond.intents.fund_with_mpp_session for Tempo session channel deposits. Harbor MPP runtime readiness is required for production enablement — see Fund Stripe MPP intents and Support matrix.
Adyen manual capture: adyen_manual_capture is a valid SettlementRail literal when the tenant has enabled the rail and saved a BYO Checkout destination. SDK callers never submit Adyen secrets; confirm readiness with paybond adyen ready / paybond adyen doctor — see Configure Adyen settlement and Support matrix.
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 (paybond.audit.exports), verifier packs, verification results, and audit export bundles 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 intent 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)
Operation label vs handler: guard_tool authorizes the operation string and requested_spend_cents you pass in. It does not inspect or constrain what handler actually does. Keep the authorization label, spend amount, and handler side effects aligned with the bound intent's allowed_tools and policy predicates. For registry-backed operation-to-handler coupling, prefer paybond.instrument() or wrap_tools().
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, Ed25519 signing, and bincode 2 wire bytes 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.