API documentation
AI Payment Guard is an authorization layer that sits between your AI agents and any payment rail (card, bank transfer, crypto). The agent asks for permission first — AI Payment Guard evaluates policies, scores risk, and optionally routes to a human approver. Only after an explicit APPROVED decision does the agent proceed to its payment provider.
We never touch your money. We decide whether your agent is allowed to.
Base URL
https://api.your-domain.com/api/v1Authentication
All requests require a Bearer token in the Authorization header. Each agent has its own key, generated from the dashboard and shown only once. Keys can be rotated at any time from Settings → Agents → Rotate key.
Authorization: Bearer pg_live_a4f2c1e9d8b7a3...1. Submit a payment intent
POST /api/v1/payment-intents
The single call your agent makes before touching any money. Returns a decision in one round-trip if a policy matches; otherwise holds the request for human review.
Required headers
Authorization: Bearer <key>Idempotency-Key: <unique string, 8–200 chars>— replaying the same key returns the original decision without creating a duplicate.Content-Type: application/json
Body
{
"amount_minor": 24900, // integer, smallest currency unit (e.g. cents)
"currency": "EUR", // ISO-4217, 3 uppercase letters
"beneficiary": {
"name": "AWS",
"account_identifier": "DE12500105170648489890", // IBAN, wallet address, account id…
"category": "infrastructure" // optional
},
"category": "infrastructure", // matched against CATEGORY_BLOCKLIST policies
"memo": "Monthly invoice", // optional — improves human-review context
"metadata": { "invoice_id": "INV-042" }, // optional, stored as-is
// ── Notification options (pick one or none) ───────────────────────────
"callback_url": "https://your-agent.example/hooks/payment-guard"
// AI Payment Guard will POST to this URL when a human decides.
// Signed with HMAC-SHA256 (see "Receiving notifications" below).
}Examples
curl -X POST https://api.your-domain.com/api/v1/payment-intents \
-H "Authorization: Bearer $PAYMENT_GUARD_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount_minor": 24900,
"currency": "EUR",
"beneficiary": {
"name": "AWS",
"account_identifier": "DE12500105170648489890"
},
"category": "infrastructure",
"memo": "Monthly invoice"
}'Response (201 Created)
{
"id": "8a4f3b1e-...",
"status": "APPROVED" | "REJECTED" | "PENDING_HUMAN_REVIEW",
"decision": "APPROVED" | "REJECTED" | "REQUIRES_HUMAN_APPROVAL",
"decisionReason": "amount_lt_500000_EUR",
"matchedPolicyId": "p1_...",
"requiresApproval": false,
"expiresAt": "2026-05-04T12:15:00.000Z" // set when APPROVED; null otherwise
}2. Handle the decision
| Decision | Status | What the agent should do |
|---|---|---|
APPROVED | APPROVED | Call your payment provider, then confirm with POST /payment-intents/:id/execute within 15 minutes. |
REJECTED | REJECTED | Final. Stop the payment — do not retry. |
REQUIRES_HUMAN_APPROVAL | PENDING_HUMAN_REVIEW | Wait for a human to decide. Choose one of the three notification strategies below. |
Waiting for human approval
When the decision is REQUIRES_HUMAN_APPROVAL, a human must approve or reject the intent in the dashboard before the agent can proceed. There are three ways to know when that happens.
Polling — simplest, no infrastructure needed
Poll GET /api/v1/payment-intents/:id every few seconds until status leaves PENDING_HUMAN_REVIEW. Works anywhere, no public URL required.
import time, requests
def wait_for_decision(intent_id: str, poll_interval: int = 5, timeout: int = 600):
deadline = time.time() + timeout
while time.time() < deadline:
resp = requests.get(
f"https://api.your-domain.com/api/v1/payment-intents/{intent_id}",
headers={"Authorization": f"Bearer {os.environ['PAYMENT_GUARD_KEY']}"},
timeout=10,
).json()
status = resp["status"]
if status == "APPROVED":
return "approved"
if status in ("REJECTED", "CANCELLED"):
return status.lower()
time.sleep(poll_interval)
raise TimeoutError("still pending")Callback URL — best for serverless & async agents
Pass a callback_url when submitting the intent. AI Payment Guard will POST to that URL the moment a human decides — signed with HMAC-SHA256 so you can verify authenticity. Ideal for Lambda functions, Cloud Run jobs, or any agent that cannot hold an open connection.
curl -X POST https://api.your-domain.com/api/v1/payment-intents \
-H "Authorization: Bearer $PAYMENT_GUARD_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount_minor": 50000,
"currency": "EUR",
"beneficiary": { "name": "Stripe", "account_identifier": "acct_1A2B3C" },
"category": "saas",
"callback_url": "https://your-agent.example/hooks/payment-guard"
}'{ event, payment_intent_id, status, decision_reason, resolved_at, comment?, reject_reason? }SSE /wait — best for synchronous agents
Open a long-lived GET connection to /api/v1/payment-intents/:id/wait. The server holds it open, polls for status changes every 2 seconds, and pushes a Server-Sent Event the moment the human decides. No public URL needed — the agent just blocks on the stream. Max wait: 10 minutes (configurable via ?timeout_ms=).
# Blocks until the human decides (or 10 min timeout)
curl -N https://api.your-domain.com/api/v1/payment-intents/$INTENT_ID/wait \
-H "Authorization: Bearer $PAYMENT_GUARD_KEY"
# Output:
# : heartbeat
# : heartbeat
# event: intent.approved
# data: {"id":"...","status":"APPROVED","decision_reason":"...","expires_at":"..."}intent.approved · intent.rejected · intent.expired · timeout. Heartbeat comments every 15s to keep proxies alive.Authorization window (15 minutes)
Every APPROVED intent carries an expires_at timestamp set 15 minutes after approval. If the agent calls POST /payment-intents/:id/execute after that window, the endpoint returns 410 Gone with code intent_expired and the intent is automatically cancelled.
// 410 Gone
{
"error": {
"code": "intent_expired",
"message": "Authorization expired at 2026-05-04T12:15:00.000Z",
"expiredAt": "2026-05-04T12:15:00.000Z"
}
}If you need more time, cancel the original intent and re-submit with a new idempotency key. The 15-minute window is intentional — stale authorizations are a common attack vector.
3. Confirm execution
POST /api/v1/payment-intents/:id/execute
Call this after your payment provider has processed the transaction. This closes the audit trail and marks the intent as EXECUTED. Only valid while status is APPROVED and expires_at has not passed.
curl -X POST https://api.your-domain.com/api/v1/payment-intents/$INTENT_ID/execute \
-H "Authorization: Bearer $PAYMENT_GUARD_KEY"Other endpoints
GET /api/v1/payment-intents/:id— current state +expires_at.GET /api/v1/payment-intents?limit=50— list intents for this agent (max 200).POST /api/v1/payment-intents/:id/cancel— cancel whilePENDING_HUMAN_REVIEWorAPPROVED.
Org-level webhooks
Register endpoints in Settings to receive all events for your organization (useful for logging pipelines, Slack alerts, etc.). Each delivery is HMAC-SHA256 signed.
Event types
intent.approved— auto-approved by policy or human approvedintent.rejected— auto-rejected by policy or human rejectedintent.requires_human— held for human reviewintent.executed— agent confirmed execution
Signature verification
import { verifyWebhookSignature } from "@payment-guard/node";
const rawBody = await request.text();
const signature = request.headers.get("x-paymentguard-signature")!;
const event = verifyWebhookSignature(rawBody, signature, process.env.WEBHOOK_SECRET!);
if (event.type === "intent.approved") {
// log, alert, trigger downstream workflow…
}Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | Body did not match the schema. |
| 400 | missing_idempotency_key | Header absent or malformed. |
| 401 | agent_auth_failed | Invalid or inactive Bearer token. |
| 404 | not_found | Intent not found or does not belong to your org. |
| 409 | invalid_state | Action not allowed in current status. |
| 410 | intent_expired | Authorization window (15 min) elapsed — intent cancelled. |
| 500 | internal_error | Unexpected server-side failure. |
// All errors follow this shape:
{
"error": {
"code": "intent_expired",
"message": "Authorization expired at 2026-05-04T12:15:00.000Z",
"details": { … } // optional
}
}Node.js SDK
npm install @payment-guard/nodeimport { PaymentGuard } from "@payment-guard/node";
const client = new PaymentGuard({ apiKey: process.env.PAYMENT_GUARD_KEY! });
const intent = await client.paymentIntents.submit({
amount_minor: 24_900,
currency: "EUR",
beneficiary: { name: "AWS", account_identifier: "DE12500105170648489890" },
category: "infrastructure",
memo: "Monthly invoice",
});
if (intent.decision === "REJECTED") {
console.log("Blocked:", intent.decisionReason);
return;
}
if (intent.decision === "REQUIRES_HUMAN_APPROVAL") {
// SSE — blocks until human decides (Option C)
await client.paymentIntents.waitForDecision(intent.id, { timeoutMs: 600_000 });
}
// ← decision is now APPROVED
// Call your payment provider here (Stripe, Wise, Coinbase, etc.)
await client.paymentIntents.execute(intent.id);Questions? support@payment-guard.example