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 outcome

Prerequisites — 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 secret

POST /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_failed

POST /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,blocked

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, 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 failed

Error 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 provider

Node.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 decision

Testing 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_reason

Need 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.