OpenClaw Skill
Spend policy enforcement for OpenClaw agents. Works with any payment method.
The openclaw-skill-pinti package adds spend policy guardrails to any OpenClaw agent. It sits between your agent's intent to pay and the actual payment execution — evaluating every spend against your configured PINTI policies.
Note
Installation
npm install openclaw-skill-pintiThe skill depends on @pinti/guard which is installed automatically as a peer dependency.
Configuration
Set your environment variables:
export PINTI_API_KEY="pinti_xxxxxxxx_..."
export PINTI_URL="https://pinti.ai" # optional, defaults to https://pinti.aiGet your API key from the PINTI Dashboard → Settings.
How It Works
Agent wants to spend $50 on OpenAI credits
↓
PINTI evaluates against your policies
↓
┌─────────────────────────────────────────┐
│ ALLOW (continue) → proceed with payment │
│ DENY (block) → stop, do not pay │
│ REQUIRE_APPROVAL → wait for human │
└─────────────────────────────────────────┘API Reference
SpendInput
All three functions accept the same input shape:
{
agentId: string; // unique agent identifier
amountMinor: number; // amount in minor units (4200 = $42.00)
unit: string; // currency code ("USD", "EUR", etc.)
merchant: string; // who is being paid
category: string; // "api", "infra", "saas", etc.
reason: string; // human-readable explanation
metadata?: Record<string, string>; // optional key-value pairs
}beforeSpendAction() — Hook Pattern
Call before any spend action. Returns an action directive: continue, block, or pause. This is the recommended pattern for most use cases.
import { beforeSpendAction } from "openclaw-skill-pinti";
const check = await beforeSpendAction({
agentId: "my-agent",
amountMinor: 5000,
unit: "USD",
merchant: "openai.com",
category: "api",
reason: "API credits top-up",
});
switch (check.action) {
case "continue":
// PINTI says ALLOW — proceed with the payment
await executePayment();
break;
case "block":
// PINTI says DENY — stop and inform user
console.log("Blocked:", check.reason);
break;
case "pause":
// PINTI says REQUIRE_APPROVAL — wait for human
console.log("Awaiting approval:", check.spendRequestId);
break;
}SpendActionResult
{
action: "continue" | "block" | "pause";
reason: string;
spendRequestId?: string;
sat?: string; // Spend Authorization Token (on "continue" only)
}Action Types
| Action | PINTI Decision | What to Do |
|---|---|---|
continue | ALLOW | Proceed with the spend action |
block | DENY (or error) | Stop. Do not execute. Inform the user. |
pause | REQUIRE_APPROVAL | Wait for human approval before proceeding. |
guardedExecutor() — Wrap and Execute
Wraps an action function with a PINTI check. The action only executes if PINTI returns ALLOW. Useful when you want a single call that handles both policy check and execution.
import { guardedExecutor } from "openclaw-skill-pinti";
const result = await guardedExecutor(
{
agentId: "my-agent",
amountMinor: 3000,
unit: "USD",
merchant: "openai.com",
category: "api",
reason: "Monthly API credits",
},
async () => {
// This only runs if PINTI allows it
return await stripe.paymentIntents.create({
amount: 3000,
currency: "usd",
});
}
);
if (result.executed) {
console.log("Payment created:", result.result);
} else {
console.log("Blocked:", result.reason);
}GuardedExecutorResult
{
executed: boolean; // true if action ran
result?: T; // return value of execute()
reason: string; // decision reason
spendRequestId?: string; // for approval tracking
}checkSpend() — Direct Evaluation
Direct policy evaluation that returns the decision status without throwing errors. Use when the hook or executor patterns don't fit your use case.
import { checkSpend } from "openclaw-skill-pinti";
const result = await checkSpend({
agentId: "agent-1",
amountMinor: 10000,
unit: "USD",
merchant: "aws.amazon.com",
category: "infra",
reason: "Compute provisioning",
});
// result.status: "allowed" | "denied" | "needs_approval"
// result.reason: "Within policy limits"
// result.sat: "eyJhbGci..." (only on "allowed")CheckSpendResult
{
status: "allowed" | "denied" | "needs_approval";
reason: string;
spendRequestId?: string;
sat?: string; // Spend Authorization Token (on "allowed" only)
}Real-World Examples
Privacy.com Virtual Card
import { beforeSpendAction } from "openclaw-skill-pinti";
// Before charging the virtual card
const check = await beforeSpendAction({
agentId: "shopping-agent",
amountMinor: 2499,
unit: "USD",
merchant: "amazon.com",
category: "shopping",
reason: "User requested: noise-cancelling headphones",
});
if (check.action === "continue") {
await privacyCard.charge({ amount: 2499, merchant: "amazon.com" });
}USDC Crypto Payment
import { guardedExecutor } from "openclaw-skill-pinti";
const result = await guardedExecutor(
{
agentId: "defi-agent",
amountMinor: 50000, // $500.00
unit: "USD",
merchant: "uniswap.org",
category: "defi",
reason: "Liquidity provision",
},
async () => {
return await wallet.sendUSDC("0xUniswap...", 500_000000);
}
);Error Handling
Note
You don't need try/catch around these functions — they never throw. Errors are caught internally and returned as block/denied results with a descriptive reason string.
// Even if PINTI API is down, this won't throw
const check = await beforeSpendAction({ ... });
// check.action === "block"
// check.reason === "Policy check failed: fetch failed"