AgentGate is an authorization layer that sits between your AI agents and any payment rail (card, bank transfer, crypto). The agent asks for permission first — AgentGate 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.
https://agentgate.eu/api/v1All 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 ag_live_a4f2c1e9d8b7a3...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.
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{
"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/agentgate"
// AgentGate will POST to this URL when a human decides.
// Signed with HMAC-SHA256 (see "Receiving notifications" below).
}curl -X POST https://agentgate.eu/api/v1/payment-intents \
-H "Authorization: Bearer $AGENTGATE_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"
}'{
"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
}| 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. |
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.
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://agentgate.eu/api/v1/payment-intents/{intent_id}",
headers={"Authorization": f"Bearer {os.environ['AGENTGATE_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")Pass a callback_url when submitting the intent. AgentGate 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://agentgate.eu/api/v1/payment-intents \
-H "Authorization: Bearer $AGENTGATE_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/agentgate"
}'{ event, payment_intent_id, status, decision_reason, resolved_at, comment?, reject_reason? }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://agentgate.eu/api/v1/payment-intents/$INTENT_ID/wait \
-H "Authorization: Bearer $AGENTGATE_KEY"
# Output:
# : heartbeat
# event: intent.approved
# data: {"id":"...","status":"APPROVED","decision_reason":"...","expires_at":"..."}intent.approved · intent.rejected · intent.expired · timeout. Heartbeat comments every 15 s to keep proxies alive.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.
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://agentgate.eu/api/v1/payment-intents/$INTENT_ID/execute \
-H "Authorization: Bearer $AGENTGATE_KEY"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 while PENDING_HUMAN_REVIEW or APPROVED.Register endpoints in Settings to receive all events for your organization (useful for logging pipelines, Slack alerts, etc.). Each delivery is HMAC-SHA256 signed.
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 executionimport { verifyWebhookSignature } from "@agentgate/node";
const rawBody = await request.text();
const signature = request.headers.get("x-agentgate-signature")!;
const event = verifyWebhookSignature(rawBody, signature, process.env.WEBHOOK_SECRET!);
if (event.type === "intent.approved") {
// log, alert, trigger downstream workflow…
}| 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
}
}npm install @agentgate/nodeimport { AgentGate } from "@agentgate/node";
const client = new AgentGate({ apiKey: process.env.AGENTGATE_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") {
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@agentgate.eu