A capability token is an intent-scoped spend authorization credential. Paybond mints it when an intent reaches a funded state (or through the sandbox guardrail bootstrap path). Your application passes it with intent_id into paybond.spendGuard(...) / paybond.spend_guard(...) so Gateway POST /verify can authorize a specific tool operation before side-effecting work runs.
Capabilities are not API keys, not model-provider credentials, and not something you generate locally or store long-term in .env as CAPABILITY_TOKEN.
What you need at runtime
Every guarded tool call needs both:
| Value | Source |
|---|---|
intent_id | Intent create, sandbox bootstrap, or MCP bootstrap response |
capability_token | Returned alongside that intent when it is funded or bootstrapped |
TypeScript:
const guard = paybond.spendGuard(intentId, capabilityToken); await guard.authorizeSpend({ operation: "travel.book_hotel", requestedSpendCents: 20_000, });
Python:
guard = paybond.spend_guard(intent_id, capability_token) await guard.authorize_spend( operation="travel.book_hotel", requested_spend_cents=20_000, )
Do not call a separate capability issuance API. Paybond does not expose POST /v1/capabilities/issue or a gateway.capabilities.issue(...) helper. The token always comes from the intent lifecycle below.
How to obtain a capability token
Pick the path that matches your environment:
| Path | When to use | How you get intent_id and capability_token |
|---|---|---|
| Sandbox guardrail bootstrap | First integration, Free Developer sandbox, any agent runtime | paybond.guardrails.bootstrapSandbox(...) (or scaffold bootstrapSandboxGuardrailIntent) |
| Production intent create | Live settlement when the rail funds immediately (stripe_connect, simulator, etc.) | paybond.intents.create(...) — read intent_id and capability_token from the response |
| Production intent fund | x402_usdc_base, delayed ACH, or any create that returns unfunded | paybond.intents.fund(...) — read capabilityToken / capability_token after funding succeeds |
| MCP host | Tool discovery over stdio or Streamable HTTP | paybond_bootstrap_sandbox_guardrail, then pass values to paybond_authorize_agent_spend |
Sandbox (fastest path)
const paybond = await Paybond.open({ apiKey: process.env.PAYBOND_API_KEY!, expectedEnvironment: "sandbox", }); const guardrail = await paybond.guardrails.bootstrapSandbox({ operation: "premium_search", requestedSpendCents: 500, currency: "usd", }); const intentId = guardrail.intent_id; const capabilityToken = guardrail.capability_token; const guard = paybond.spendGuard(intentId, capabilityToken);
Python parity: await paybond.guardrails.bootstrap_sandbox(...).
See One-command guardrails for the generated scaffold.
Production create (immediate fund)
When the selected settlement rail places a hold and funds the intent in the same create response, Paybond mints the capability token automatically:
const created = await paybond.intents.create({ /* principal, payee, budget, predicate, allowedTools, settlementRail, recognitionProof, ... */ }); const intentId = String(created.intent_id); const capabilityToken = String(created.capability_token ?? ""); if (!capabilityToken) { throw new Error( "intent created without capability_token; fund the intent before guarding tools", ); }
Production fund (x402 or delayed funding)
If create does not return a token, fund first. On x402_usdc_base, prefer paybond.intents.fundWithX402 / fund_with_x402 (handles 402 signing and polling). On stripe_mpp, prefer paybond.intents.fundWithMppCharge / fundWithMppSession or fund_with_mpp_charge / fund_with_mpp_session (handles Payment Auth credential creation and polling):
const funded = await paybond.intents.fundWithX402({ intentId, recognitionProof: fundProof, signPayment: (challenge) => x402Wallet.signPayment(challenge), issueRecognitionProof: (envelope) => issueAgentRecognitionProofV1({ /* bind envelope */ }), }); const capabilityToken = funded.capabilityToken;
See Stablecoin funding for manual /fund control and local WireMock/offline mocks. See MPP funding for stripe_mpp Payment Auth helpers.
Agent run lifecycle
For any agent runtime — OpenAI, Gemini, Claude/Anthropic, Vercel AI SDK, LangGraph, MCP, or a custom orchestrator — bind one intent per agent run (or per user task), not one global env var:
Agent run starts │ ├─ Sandbox: paybond.guardrails.bootstrapSandbox(...) └─ Production: paybond.intents.create(...) → fund if needed │ ▼ Read intent_id and capability_token │ ▼ paybond.spendGuard(intentId, capabilityToken) │ ├─ authorizeSpend / guardTool before each paid tool │ ▼ Execute paid API / vendor action │ ▼ paybond.intents.submitEvidence(...) or sandbox submitSandboxEvidence
Store intent_id and capability_token in run context (closure, request scope, graph state, MCP tool args). Rotate them when you create a new intent for a new run.
Verification pattern
- Bind execution to
(tenant, intent_id, capability_token)— tenant comes fromPaybond.open, not from user input. - Call
authorizeSpend/authorize_spendorguardTool/guard_toolfor the exactoperationstring listed on the intent. - Only run the tool when verification returns
allow: true. - Submit evidence against the same
intent_idafter guarded work completes.
A token minted for one intent must never be reused for another intent, even inside the same tenant.
Gateway may return HTTP 200 with allow: false for a business denial or approval hold. That is not a transport error. See Agent integrations — approval holds.
For stripe_mpp session intents, POST /verify may also require a Tempo voucher credential (mpp_voucher_required until your wallet supplies one). Cumulative voucher spend is capped at the signed intent amount_cents; Paybond rejects over-budget vouchers without writing pending state. See Session voucher metering at verify.
Choose operation names deliberately
allowed_tools / allowedTools use your operation names:
travel.planner.planpremium_searchcrm.contact.enrich
Paybond does not define the catalog. Paybond compares the operation you pass at verify time with the names attached to the intent.
Incorrect integration patterns
Capability tokens come from the intent lifecycle, not from a separate issuance API or long-lived configuration. If an integration does not match the flows above, check these mismatches first:
| Incorrect pattern | Correct approach |
|---|---|
process.env.CAPABILITY_TOKEN | Read token from bootstrap/create/fund response for this run |
POST /v1/capabilities/issue | Does not exist — use intent lifecycle |
paybond.spendGuard({ capabilityToken }) | paybond.spendGuard(intentId, capabilityToken) — two positional arguments |
| Reusing one token across intents | One capability binding per intent |
| Skipping fund on x402 / delayed rails | Call paybond.intents.fund(...) until capabilityToken is present |
Treating Paybond as a model-provider baseURL | Model client stays on OpenAI, Gemini, Claude, or your host; Paybond guards tools only — see Endpoints & environments |
Design recommendations
- Keep operation names stable so policy and reporting stay readable.
- Verify as close as possible to execution time.
- Keep one capability binding per active run or workflow step.
- Treat a deny response as a business decision, not as a transport error.
- Never log capability tokens; treat them like session secrets.
Implementation guides
- TypeScript quickstart — capability token source
- Python quickstart — capability token source
- Agent integrations — runtime-neutral pattern and framework scaffolds
- MCP server — bootstrap and authorize tools over stdio or Streamable HTTP
- Harbor API — funding and verify HTTP contract behind the SDK