Execution Modes
PINTI supports three execution modes. Choose based on your enforcement needs and how your agent handles payments.
Overview
| Mode | Handle Type | Enforcement | Best For |
|---|---|---|---|
| SDK Guard | sdk | Advisory + audit | Agents with existing payment credentials |
| External Executor | external_executor | Hard (HMAC-signed) | Custom payment backends |
| Managed | managed_stripe / managed_moonpay | Hard (PINTI owns execution) | Zero-config enterprise (coming soon) |
Warning
Hard enforcement requires that payment credentials are not exposed to the agent runtime. If your agent has direct access to raw API keys or card numbers, enforcement is advisory only. For hard enforcement, use External Executor or Managed mode.
SDK Guard Mode
Your agent holds its own payment credentials and executes payments directly. PINTI provides server-side policy enforcement (at the evaluate step) and anomaly detection via receipt comparison.
- Install
@pinti/guardin your agent - Call
pinti.authorize()before each payment — PINTI evaluates policy and returns a SAT - Execute the payment on any rail (Stripe, MoonPay, EVM, etc.)
- Call
pinti.submitReceipt()after payment - PINTI compares receipt vs SAT → flags anomalies
Note
If your agent bypasses the SDK and pays without calling
authorize(), PINTI detects the missing receipt after the expected window passes. See the SDK documentation for full usage examples.External Executor Mode
- Create a payment handle in Settings. You provide your executor backend URL and an auth token. These are encrypted at rest and never shown again.
- Agent receives only the handle ID — a short opaque string. No URLs, no tokens, no credentials.
- Agent calls
POST /api/v1/spend/evaluate— PINTI evaluates policy and returns a SAT. - Agent calls
POST /api/v1/payment/executewith the handle ID, SAT, and payment parameters. - PINTI verifies the SAT, atomically consumes it, then calls your executor backend server-to-server with a signed request.
- Your backend executes the actual payment and returns the result. Zero credential exposure.
Note
Your executor endpoint must not be reachable by the agent runtime. Deploy your executor in a network segment the agent cannot access.
API: Execute Payment
POST /api/v1/payment/execute
// Request
POST /api/v1/payment/execute
x-api-key: pinti_xxxxxxxx_xxxxxxxxxxxxxxxxxx
{
"paymentHandleId": "clxyz...",
"sat": "eyJ2ZXJzaW9uIjox...",
"params": {
"amountMinor": 5000,
"currency": "usd",
"description": "Monthly API credits"
}
}
// Success Response (200)
{
"executed": true,
"spendRequestId": "cm...",
"transaction": {
"id": "tx_xxxxxxxxxx",
"status": "completed"
}
}
// Error Responses
// 400 — SDK mode SAT (use /spend/receipt instead)
// 403 — Handle not found, inactive, or mode mismatch
// 409 — SAT already consumed or expired
// 502 — Executor returned error (SAT stays consumed)
// 504 — Executor unreachable (retryable with same SAT JTI)Verifying PINTI Requests
When PINTI calls your executor backend, it includes two headers for request authentication:
X-Pinti-Timestamp— Unix timestamp of the requestX-Pinti-Signature— HMAC-SHA256 oftimestamp.bodyusing your signing secret
Your executor should:
- Reject requests where the timestamp drifts more than 5 minutes
- Recompute the HMAC and compare with the signature header
- Use the
satJtifield as an idempotency key
Warning
MUST: Use satJti as idempotency key. If PINTI crashes after your executor succeeds, PINTI will retry with the same satJti. Without idempotency on your side, this will cause double charges. This is not optional. This is required for enforcement integrity.
Executor verification example (Node.js)
import crypto from "crypto";
function verifyPintiRequest(req, signingSecret) {
const timestamp = req.headers["x-pinti-timestamp"];
const signature = req.headers["x-pinti-signature"];
const body = JSON.stringify(req.body);
// 1. Check timestamp drift (±5 minutes)
const drift = Math.abs(Date.now() / 1000 - Number(timestamp));
if (drift > 300) return false;
// 2. Verify HMAC
const expected = crypto
.createHmac("sha256", signingSecret)
.update(timestamp + "." + body)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature, "hex"),
Buffer.from(expected, "hex")
);
}Managed Mode (Coming Soon)
PINTI owns the execution. You connect your provider (e.g. Stripe restricted key), and PINTI executes payments directly. The agent sees only the handle ID and SAT. Zero credential exposure, zero executor backend to build.
Enforcement Guarantees
| Condition | Result |
|---|---|
| No SAT | No payment execution |
| Consumed SAT | 409 — cannot replay |
| Expired SAT | 400 — rejected |
| Amount/currency mismatch | 403 — blocked |
| SDK mode SAT on /execute | 400 — wrong endpoint |
| Mode ↔ handle type mismatch | 403 — blocked |
| Missing receipt (SDK mode) | Anomaly flagged in dashboard |
| Agent has raw credentials | Advisory — use SDK Guard for audit trail |
| Agent only has handle ID | Hard enforce — must go through PINTI |
Tip
PINTI enforces payments by owning the execution surface. If you give your agent direct payment credentials, use SDK Guard mode for advisory enforcement and anomaly detection.