PaynorAI API Documentation

Agent-native payment control with policy checks, approvals, provider execution handoff, and audit trails.

Quick Start — get your agent paying in 5 minutes

First ask PaynorAI for clearance. If approved, your own secure backend charges Stripe/Razorpay, then reports the result back.

curl -X POST /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

Use your agent key server-side only. PaynorAI hashes the key and matches it to the agent policy before any payment is cleared for your provider.

Authorization model
1. Create an agent in the dashboard
2. Copy the key once: agp_live_...
3. Store it in your server secrets
4. Send it as agent_key with each payment request

POST /pay — request + response

Approved requests are only cleared for execution. Money moves later through your own Stripe/Razorpay backend, never inside PaynorAI.

POST /api/public/pay

Request
{
  "agent_key": "agp_live_xk29...",
  "merchant": "aws.amazon.com",
  "merchant_category": "Cloud",
  "amount": 39,
  "currency": "USD",
  "provider": "stripe",
  "idempotency_key": "order_123"
}

Approved response
{
  "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 from your secure backend, then report the provider result back to PaynorAI."
}

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
   - If blocked: stop, do not charge
   - If pending_approval: wait, do not charge
   - If approved: continue

2. Execute the charge in your secure backend
   - Stripe PaymentIntent / invoice / subscription
   - Razorpay order / payment / mandate

3. POST /api/public/provider-result
   - Save provider_transaction_id
   - Save receipt_url
   - Mark provider_succeeded or provider_failed

POST /provider-result — report real provider outcome

After your backend executes the real Stripe/Razorpay charge, report the provider result so PaynorAI keeps the audit trail accurate.

POST /api/public/provider-result
{
  "agent_key": "agp_live_xk29...",
  "transaction_id": "tx_...",
  "provider": "stripe",
  "provider_transaction_id": "pi_123",
  "status": "succeeded",
  "receipt_url": "https://pay.stripe.com/receipts/..."
}

Response
{
  "status": "provider_succeeded",
  "transaction_id": "tx_...",
  "provider": "stripe",
  "provider_transaction_id": "pi_123"
}

Do 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
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

approved = provider may now charge
provider_succeeded = real provider payment succeeded

GET /tx/:id — check transaction status

Poll a transaction to know whether it was approved, blocked, rejected, or is still waiting for human review.

GET /api/public/check-tx/TRANSACTION_ID?agent_key=agp_live_xk29...

{
  "id": "TRANSACTION_ID",
  "status": "pending_approval",
  "policy_reason": "Above approval threshold",
  "approved_at": null
}

Approvals — 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.

POST /api/public/approve-tx
{
  "transaction_id": "tx_...",
  "action": "approve",
  "agent_key": "agp_live_xk29..."
}

Error codes — full list with explanations

Use these responses to decide whether the agent should retry, ask a human, or stop the purchase.

400 invalid_request           Missing or invalid request fields
401 invalid_agent_key        Key does not match an active agent
403 agent_paused             Agent has been disabled
403 per_tx_limit_exceeded    Amount is above the single payment cap
403 monthly_limit_exceeded   Agent budget is exhausted
403 blocked_merchant         Merchant is not allowed
403 category_not_allowed     Merchant category is not allowed
403 provider_not_connected   Provider is not connected for this owner
409 transaction_not_pending  Approval was already resolved
409 transaction_not_provider_ready  Charge result reported before approval
202 pending_approval         Human approval is required
provider_succeeded           Real provider payment succeeded
provider_failed              Real provider payment failed

SDK-style JavaScript snippet

Call PaynorAI from your own backend so the agent key never appears in browser code.

async function payForAgent({ merchant, amount, orderId }) {
  const res = await fetch("/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 result = await res.json();
  if (result.status !== "approved") return result;

  const providerPayment = await chargeWithStripeOrRazorpay(result.execute_with_provider);

  await fetch("/api/public/provider-result", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      agent_key: process.env.PAYNORAI_AGENT_KEY,
      transaction_id: result.transaction_id,
      provider: result.provider,
      provider_transaction_id: providerPayment.id,
      status: providerPayment.succeeded ? "succeeded" : "failed",
      receipt_url: providerPayment.receipt_url,
    }),
  });

  return result;
}