PINTI Docs

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

PINTI is not a payment method. It's a policy layer that decides whether a payment should happen. It works with any payment rail your agent uses — Privacy.com cards, Privy wallets, USDC, Stripe, or anything else.

Installation

npm install openclaw-skill-pinti

The 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.ai

Get your API key from the PINTI Dashboard → Settings.

How It Works

Agent → PINTI → Payment
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:

SpendInput
{
  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.

Hook pattern
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

Return type
{
  action: "continue" | "block" | "pause";
  reason: string;
  spendRequestId?: string;
  sat?: string;  // Spend Authorization Token (on "continue" only)
}

Action Types

ActionPINTI DecisionWhat to Do
continueALLOWProceed with the spend action
blockDENY (or error)Stop. Do not execute. Inform the user.
pauseREQUIRE_APPROVALWait 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.

Guarded executor
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

Return type
{
  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.

Direct evaluation
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

Return type
{
  status: "allowed" | "denied" | "needs_approval";
  reason: string;
  spendRequestId?: string;
  sat?: string;  // Spend Authorization Token (on "allowed" only)
}

Real-World Examples

Privacy.com Virtual Card

Guard a Privacy.com card spend
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

Guard a USDC transfer
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

On API errors, all functions default to block/denied (fail-closed). This ensures agents never spend money when the policy check fails.

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.

Error handling is built in
// Even if PINTI API is down, this won't throw
const check = await beforeSpendAction({ ... });
// check.action === "block"
// check.reason === "Policy check failed: fetch failed"