Documentation

Agent payment

An agent pays with the partner's prepaid wallet. It never receives card data, never calls Stripe directly, and never creates a payment method. The principal funds the wallet; the agent spends within a mandate.

Live payment path

  1. Read GET /v1/capabilities and GET /v1/agent-manifest.json for flags, limits and URLs.
  2. Read GET /v1/catalog and choose an available offerId.
  3. Check the quote or expected total against the mandate.
  4. Call POST /v1/orders with a stable reference, a unique Idempotency-Key and either a quoteId or the one-call currency, items and expectedTotalMinor.
  5. Poll GET /v1/orders/{purchaseId} until completed, failed or the target partially_fulfilled.
  6. Redeem with POST /v1/orders/{purchaseId}/redeem into an entitled channel.

The wallet is debited inside the purchase transaction. A failed order creates no purchase and no debit. A processing order is already paid and will be fulfilled asynchronously.

Current and target states

BehaviourCurrentTarget
Insufficient wallet credit402 insufficient_funds402 payment_required with funding_url
Order above approval threshold409 approval_required; no order and no debitAdd a human approval exchange
Agent per-order / daily / rolling limitsEnforced before debitSame
Bulk line and unit capsPrincipal 200 lines / 5,000 units enforcedAgent 50 lines / 500 units unless raised
Order statusesprocessing, completed, failedAdds partially_fulfilled
Funding an agentPrincipal funds the walletA capped off-session mandate may be added

Agent caps and threshold enforcement are Current. Handle agent_budget, agent_inactive and approval_required; do not retry an approval failure as a transient error.

Budget check before ordering

The agent must not rely on the server to enforce its budget yet. Apply all three limits and the approval threshold before calling the order endpoint:

TypeScriptCode
type AgentMandate = { perOrderLimitMinor: number; dailyLimitMinor: number; rollingLimitMinor: number; approvalThresholdMinor: number; }; export function decidePurchase(input: { mandate: AgentMandate; totalMinor: number; spentTodayMinor: number; spentRollingMinor: number; }) { const { mandate, totalMinor } = input; if (totalMinor > mandate.perOrderLimitMinor) return "deny_per_order"; if (input.spentTodayMinor + totalMinor > mandate.dailyLimitMinor) return "deny_daily"; if (input.spentRollingMinor + totalMinor > mandate.rollingLimitMinor) return "deny_rolling"; if (totalMinor >= mandate.approvalThresholdMinor) return "request_approval"; return "allow"; }

Keep the counters in durable storage keyed by agent and UTC day. Do not keep them only in process memory across restarts.

One-call payment

Code
POST /v1/orders Idempotency-Key: agent-2026-11-04-0001
JSONCode
{ "reference": "field-kit-2026-11-04-0001", "currency": "USD", "expectedTotalMinor": 1618, "items": [ { "offerId": "11111111-1111-4111-8111-111111111111", "quantity": 2 } ] }

expectedTotalMinor is the agent's ceiling check. If the fresh total differs, the API returns 409 price_changed with the new totalMinor and a fresh quote, and does not charge. The agent must not retry with a raised total unless the mandate covers it.

Quote-based payment

Use this when a human reviews the total or the agent needs a stable, inspectable object before purchase:

Code
POST /v1/quotes
JSONCode
{ "currency": "USD", "items": [{ "offerId": "11111111-1111-4111-8111-111111111111", "quantity": 2 }] }

The quote lasts 15 minutes. Review expiresAt, currency, totalMinor and capped. If the decision takes longer, request a new quote rather than extending the old one.

Recovery rules

402 insufficient_funds or payment_required

  • Do not retry the order.
  • insufficient_funds: report the shortfall to the principal and stop.
  • payment_required: surface funding_url to the principal and stop.
  • Never ask for card details, never call Stripe, and never create a second funding session.
  • Resume only after the wallet shows available credit.

409 price_changed

  • Do not retry with the same expected total.
  • Read the returned totalMinor and quote.
  • If the new total is within the mandate, make a new decision and a new order with a new idempotency key. If outside the mandate, stop and escalate.

409 quote_expired

  • Create a new quote.
  • Do not reuse the expired quote id.

429 rate_limited or quota_exceeded

  • Read Retry-After. Wait, add jitter, and retry with the same idempotency key and body.
  • If the daily quota is exhausted, stop and report the reset time.
  • Do not open a second key to evade a quota.

403 ip_not_allowed

  • Stop. The egress address is not on the key allowlist.
  • Do not rotate the key or the allowlist yourself.

500 internal_error

  • Check GET /v1/orders/{purchaseId} or GET /v1/orders before retrying.
  • Retry only with the same idempotency key after confirming no order or charge exists.

Funding authority

  • A catalogue or order scope does not grant funding authority.
  • Do not give an agent wallet:fund unless the principal explicitly delegates top-ups.
  • Do not raise the agent budget, approval threshold or expiry.
  • Do not create another agent or key.
  • Do not choose an arbitrary payment source. The wallet currency must match the quote currency.

Redemption and secrets

An agent may receive LPA text or QR SVG only in a server-side process that writes to the principal's entitled delivery channel. It must not print, log, cache, forward or summarise the credential in model output. If the delivery channel is not known, stop before redeeming.