PaynorAI · API Reference
PaynorAI API Documentation
Agent-native payment control with policy checks, approvals, provider execution handoff, and a complete audit trail.
Base URL https://paynorai.com
Overview — what PaynorAI is (and is not)
PaynorAI is the approval gate for AI-agent payments. Your agent asks PaynorAI for clearance against your spend policy. If approved, your own backend executes the real charge with Stripe or Razorpay, then reports the result back so PaynorAI keeps a complete audit trail. PaynorAI never holds money, card data, or mandates.
Base URL
https://paynorai.com
Content-Type
All POST requests must send: Content-Type: application/json
Auth model
agent_key is sent in the JSON body (server-to-server only).
Never expose agent_key in browser code, mobile apps, or public repos.
Three-step flow
1. POST /api/public/pay -> approval decision
2. Charge with Stripe / Razorpay -> from your secure backend
3. POST /api/public/provider-result -> report real outcomePrerequisites — before your first call
PaynorAI will only clear payments for agents that have an active key, an attached spend policy, and a connected payment provider for the same owner.
Checklist
[ ] Created an agent in the dashboard
[ ] Copied the agent_key once (starts with agp_live_...)
[ ] Stored the key in your server secrets (never in the browser)
[ ] Attached a spend policy (per_tx_limit, monthly_limit, approval_threshold)
[ ] Connected a provider (stripe or razorpay) under Settings -> Providers
Without a connected provider, /pay returns 403 provider_not_connected.Quick Start — get your agent paying in 5 minutes
Ask PaynorAI for clearance first. If the response is approved, your backend executes the charge with the connected provider, then reports the outcome.
curl -X POST https://paynorai.com/api/public/pay \
-H "Content-Type: application/json" \
-d '{
"agent_key": "agp_live_xk29...",
"merchant": "aws.amazon.com",
"merchant_category": "Cloud",
"amount": 39,
"currency": "USD",
"provider": "stripe",
"idempotency_key": "order_123"
}'Authentication — how agent_key works
Each agent has a unique key. PaynorAI stores only a SHA-256 hash of the key, matches it to the agent on every request, and enforces that agent's spend policy before clearing the payment.
Authorization model
1. Create an agent in the dashboard
2. Copy the key once: agp_live_...
3. Store it in your server secrets (env var, secret manager)
4. Send it as "agent_key" in the JSON body of each request
Key rotation
- Disable the old agent in the dashboard
- Create a new agent + new key
- Roll the new key into your server secretPOST /api/public/pay — request approval
The core endpoint. Returns approved, blocked, or pending_approval. Money never moves here — only a policy decision is made.
POST https://paynorai.com/api/public/pay
Content-Type: application/json
Request body
{
"agent_key": "agp_live_xk29...", // required
"merchant": "aws.amazon.com", // required
"merchant_category": "Cloud", // required
"amount": 39, // required, > 0
"currency": "USD", // ISO 4217, 3 letters
"provider": "stripe", // must be a connected provider
"idempotency_key": "order_123", // unique per attempt, 8-120 chars
"metadata": { "order_id": "abc" } // optional, string|number|boolean
}
Approved response (200)
{
"status": "approved",
"transaction_id": "tx_...",
"provider": "stripe",
"execute_with_provider": {
"provider": "stripe",
"merchant": "aws.amazon.com",
"amount": 39,
"currency": "USD",
"idempotency_key": "order_123"
},
"next_step": "execute_provider_charge",
"report_result_url": "/api/public/provider-result",
"message": "Policy cleared. Execute this payment with the connected provider..."
}POST /pay — blocked & pending_approval responses
Plan for these two outcomes. Blocked means stop. Pending means wait for a human approval before reading /check-tx again.
Blocked response (403)
{
"status": "blocked",
"transaction_id": "tx_...",
"reason": "monthly_limit_exceeded",
"next_step": "do_not_charge",
"message": "Policy blocked this request. Do not execute a provider charge."
}
Pending approval response (202)
{
"status": "pending_approval",
"transaction_id": "tx_...",
"provider": "stripe",
"next_step": "wait_for_approval",
"message": "Human approval is required. Do not execute a provider charge yet."
}
Idempotent replay (same agent_id + idempotency_key)
{
"status": "approved", // or whatever the original outcome was
"transaction_id": "tx_...",
"provider": "stripe",
"approved_at": "2026-05-03T10:21:00Z",
"idempotent": true,
"next_step": "execute_provider_charge"
}Provider execution — the 3-step flow
PaynorAI is the approval gate, not the money mover. Charge cards, banks, UPI, or mandates only from your own secure provider integration after approval.
1. POST /api/public/pay
- blocked -> stop, do not charge
- pending_approval -> wait, do not charge
- approved -> continue
2. Execute the charge in your secure backend
- Stripe: PaymentIntent / Invoice / Subscription
- Razorpay: Order / Payment / Mandate
- Reuse the idempotency_key from PaynorAI
3. POST /api/public/provider-result
- Save provider_transaction_id
- Save receipt_url
- Mark provider_succeeded or provider_failedPOST /api/public/provider-result — report real outcome
After your backend executes the real Stripe/Razorpay charge, report the provider result so PaynorAI keeps the audit trail accurate. Only approved transactions can be reported.
POST https://paynorai.com/api/public/provider-result
Content-Type: application/json
Request body
{
"agent_key": "agp_live_xk29...", // required
"transaction_id": "tx_...", // PaynorAI tx id
"provider": "stripe", // must match the original
"provider_transaction_id": "pi_123", // required when status=succeeded
"status": "succeeded", // or "failed"
"receipt_url": "https://pay.stripe.com/receipts/...", // optional
"failure_reason": "card_declined" // optional, on failure
}
Response (200)
{
"status": "provider_succeeded", // or "provider_failed"
"transaction_id": "tx_...",
"provider": "stripe",
"provider_transaction_id": "pi_123",
"receipt_url": "https://pay.stripe.com/receipts/..."
}GET /api/public/check-tx/:id — poll status
Poll a transaction to know whether it was approved, blocked, rejected, or is still waiting for human review. Pass the agent_key as a query string.
GET https://paynorai.com/api/public/check-tx/TRANSACTION_ID?agent_key=agp_live_xk29...
Response
{
"id": "TRANSACTION_ID",
"status": "pending_approval",
"block_reason": null,
"policy_reason": "Above approval threshold",
"approved_at": null,
"provider": "stripe",
"provider_transaction_id": null,
"idempotency_key": "order_123",
"next_step": "wait_for_approval"
}POST /api/public/approve-tx — release or reject a held payment
When a transaction crosses the approval threshold, send an approval decision before your connected provider is allowed to execute it. Approving returns the same execute_with_provider payload as /pay.
POST https://paynorai.com/api/public/approve-tx
Content-Type: application/json
{
"transaction_id": "tx_...",
"action": "approve", // or "reject"
"agent_key": "agp_live_xk29..."
}
Approved response
{
"status": "approved",
"transaction_id": "tx_...",
"provider": "stripe",
"next_step": "execute_provider_charge",
"report_result_url": "/api/public/provider-result",
"execute_with_provider": { "provider": "stripe", "merchant": "...", "amount": 200, "currency": "USD", "idempotency_key": "order_123" }
}
Rejected response
{ "status": "rejected", "next_step": "do_not_charge" }GET /api/public/export-csv — export the audit trail
Download every transaction for an owner as CSV. Useful for finance, reconciliation, and compliance reviews.
GET https://paynorai.com/api/public/export-csv?owner_id=OWNER_UUID
Response
Content-Type: text/csv
Content-Disposition: attachment; filename=agentpay-export.csv
Date,Agent,Merchant,Category,Amount (USD),Status
2026-05-03T10:21:00Z,Ops Bot,aws.amazon.com,Cloud,39,provider_succeeded
2026-05-03T09:14:00Z,Ops Bot,unknown-vendor.io,Other,820,blockedDo not do these things
These rules keep PaynorAI as a control layer instead of turning it into a payment processor or wallet.
Do not put agent_key in browser code, mobile apps, or public repos
Do not charge before PaynorAI returns "approved"
Do not store card, bank, CVV, UPI, or mandate secrets in PaynorAI
Do not treat "approved" as "paid"
Do not top up funds into PaynorAI
Do not reuse an idempotency_key across logically different orders
approved = provider may now charge
provider_succeeded = real provider payment succeeded
provider_failed = real provider payment failedError codes — full list with explanations
Use these responses to decide whether the agent should retry, ask a human, or stop the purchase.
HTTP Code Meaning
200 ok Decision returned (approved | provider_succeeded | etc.)
202 pending_approval Human approval is required
400 invalid_request Missing or invalid request fields (see "details")
400 provider_transaction_id_required succeeded result without a provider tx id
401 agent_key_required agent_key missing on /check-tx
401 invalid_agent_key Key does not match an active agent
403 agent_paused Agent has been disabled
403 per_tx_limit_exceeded Amount above the single payment cap
403 monthly_limit_exceeded Agent monthly budget is exhausted
403 blocked_merchant Merchant is on the blocklist
403 category_not_allowed Merchant category is not allowed
403 provider_not_connected Provider is not connected for this owner
403 policy_missing Agent has no spend policy attached
404 not_found transaction_id does not exist
409 transaction_not_pending Approval was already resolved
409 transaction_not_provider_ready Charge result reported before approval
409 provider_mismatch Reported provider != original providerNode.js — end-to-end snippet
Call PaynorAI from your own backend so the agent key never appears in browser code.
// server-side only
async function payForAgent({ merchant, amount, orderId }) {
const res = await fetch("https://paynorai.com/api/public/pay", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
agent_key: process.env.PAYNORAI_AGENT_KEY,
merchant,
merchant_category: "Cloud",
amount,
currency: "USD",
provider: "stripe",
idempotency_key: orderId,
}),
});
const decision = await res.json();
if (decision.status !== "approved") return decision;
// Charge with your own Stripe / Razorpay integration
const providerPayment = await chargeWithStripe(decision.execute_with_provider);
await fetch("https://paynorai.com/api/public/provider-result", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
agent_key: process.env.PAYNORAI_AGENT_KEY,
transaction_id: decision.transaction_id,
provider: decision.provider,
provider_transaction_id: providerPayment.id,
status: providerPayment.succeeded ? "succeeded" : "failed",
receipt_url: providerPayment.receipt_url,
}),
});
return decision;
}Python — end-to-end snippet
Same flow in Python using requests. Keep the agent_key in an env var or a secret manager.
import os, requests
BASE = "https://paynorai.com"
KEY = os.environ["PAYNORAI_AGENT_KEY"]
def pay_for_agent(merchant: str, amount: float, order_id: str):
decision = requests.post(f"{BASE}/api/public/pay", json={
"agent_key": KEY,
"merchant": merchant,
"merchant_category": "Cloud",
"amount": amount,
"currency": "USD",
"provider": "stripe",
"idempotency_key": order_id,
}).json()
if decision.get("status") != "approved":
return decision
provider_payment = charge_with_stripe(decision["execute_with_provider"])
requests.post(f"{BASE}/api/public/provider-result", json={
"agent_key": KEY,
"transaction_id": decision["transaction_id"],
"provider": decision["provider"],
"provider_transaction_id": provider_payment["id"],
"status": "succeeded" if provider_payment["succeeded"] else "failed",
"receipt_url": provider_payment.get("receipt_url"),
})
return decisionTesting tips — exercise every branch
Before you go live, prove each policy outcome at least once. The dashboard's Transactions view shows the resulting state in real time.
1. Approved -> small amount under per_tx_limit and approval_threshold
2. Pending -> amount above approval_threshold, then approve in dashboard
3. Blocked -> amount above per_tx_limit OR a blocked merchant
4. Rejected -> trigger pending_approval, then reject in dashboard
5. Idempotent -> repeat the same idempotency_key, expect "idempotent": true
6. Provider OK -> POST /provider-result with status=succeeded
7. Provider fail -> POST /provider-result with status=failed + failure_reasonNeed help?
Spin up an agent in the dashboard, copy the key, and run the Quick Start curl above. If a request looks wrong, the Transactions screen shows the exact policy reason for every decision.