Your AI Agent Is Authenticated. What Is It Allowed to Buy?
Authentication is not authorization to spend. Per-purchase caps, cumulative budgets, retries and concurrency, with a documented AWS AgentCore example and OAuth 2.0 and OpenAI ACP context.
An agent presents a valid OAuth token. Your API accepts it. That settled one thing: the caller is who the token says it is. It did not settle whether this agent may buy this item from this merchant for this reason right now.
Authentication, delegated authority, purchase approval and payment authorization are separate events with separate evidence. This post shows where each check lives, using a fictional scenario.
The scenario (fictional)
A research agent at a made-up company, Northwind Labs, asks to buy a market report. The request:
{
"agent_id": "agent-research-07",
"merchant": "reports.example-vendor.test",
"amount": 42.00,
"currency": "USD",
"purpose": "Q4 competitor analysis (approved project P-1187)",
"expires_at": "2026-10-08T18:00:00Z"
}
The policy attached to that agent: maximum $50 per purchase, merchants limited to an allow-list, purpose must match an approved project, requests expire, and anything over $25 needs a named human approver.
Where each check happens
| Event | Question | Who establishes it | Evidence |
|---|---|---|---|
| Authentication | Who is calling? | Your identity layer | Token, client credentials |
| Delegated authority | What was this agent given the right to do? | The agent's owner, in your policy store | Policy record, owner, scope |
| Purchase approval | Did an accountable person OK this purchase? | Your application | Approval record tied to request |
| Payment authorization | Did the payment network or PSP accept it? | PSP / chain | Authorization ID |
An OAuth token can carry scopes, but a scope like purchase does not encode "$50 at this merchant for project P-1187." That logic lives in your application.
Application-side policy check
def authorize_purchase(req, policy, ledger, approvals):
if req.merchant not in policy.allowed_merchants: raise Denied("merchant")
if req.purpose_project not in policy.approved_projects: raise Denied("purpose")
if req.expires_at <= now(): raise Denied("expired")
if req.amount > policy.per_purchase_cap: raise Denied("per-purchase cap")
if req.amount > policy.approval_threshold:
if not approvals.has_valid(req.request_id): raise NeedsApproval()
# cumulative budget: reserve atomically, don't check-then-spend
if not ledger.reserve(req.agent_id, req.request_id, req.amount):
raise Denied("budget exhausted")
return Allowed(req.request_id)
The atomic ledger.reserve call at the end is the important part; see the section on concurrent purchases below.
Per-purchase cap vs cumulative budget
They guard different failures.
- A per-purchase cap ($50) limits the damage of one bad decision.
- A cumulative budget (say $200 per run or per month) limits the sum of many small decisions, each individually legal.
An agent that stays under $50 forty times has spent $2,000. Only the cumulative control sees that.
Retries and concurrent purchases
Agents retry, and they run in parallel. Two failure modes follow:
- Retry double-spend. If a network timeout hides a success, the agent retries. Key every purchase by a stable
request_idand make the ledger and the downstream call idempotent on that key. OpenAI's ACP specs require anIdempotency-Keyheader, and the Delegated Payment Spec returns a 409 when the same key arrives with different parameters. - Concurrent overspend. Two parallel requests each read "$30 remaining" and each spend $25. Check-then-act races like this are solved by atomically reserving budget before the payment call and releasing it if the payment fails.
A documented example, not a ContextIQ feature
OpenAI's controlled agentic commerce cookbook demonstrates this layering with AWS AgentCore Payments. In the example, a per-transaction maximum_amount and a per-run cumulative limit are both enforced by the application, human approval is recorded as an ApprovalGrant with approved_by and approved_at, and an unapproved request fails with PolicyDenied. The cookbook states that the application checks merchant, purpose, amount and approval before a payment can occur, and its sample runs on synthetic payments unless operators opt in. We cite it as a pattern; ContextIQ is not part of it.
Likewise, OpenAI's Delegated Payment Spec lets a payment credential carry an allowance (maximum amount, currency, merchant, checkout session, expiry). That limits the credential, but it does not replace your own policy: your application still decides whether to ask for the credential at all.
The pending-approval pattern
The OpenID Foundation's AuthZEN working group has approved a draft Access Request and Approval Profile (AARP) for the case where policy cannot yet authorize an action because an approval, consent or delegated authority is missing; the system signals what is pending and re-evaluates once it is satisfied (OIDF announcement). It is a working-group draft, and the NeedsApproval branch above is our own simplification of the same idea, not an implementation of AARP.
Developer checklist
- Authenticate the agent, then look up its policy by agent identity, not by anything in the request body.
- Name an accountable human owner for every agent identity.
- Validate merchant, purpose, amount, currency and expiry server-side.
- Enforce a per-purchase cap and a cumulative budget.
- Require approval above a threshold, and bind each approval to one request ID.
- Reserve budget atomically; release on failure.
- Use idempotency keys end to end.
- Log the decision and its inputs, including denials.
Where ContextIQ fits
ContextIQ does not authorize purchases or enforce spending limits; that belongs to your application. What it offers is endpoint inspection: the Agent Protocol Inspector can scan an MCP, A2A or ARD endpoint, and the x402 Inspector can validate a payment endpoint's 402 challenge before your policy code decides whether to pay it.
Sources
Follow Trango Compute on LinkedIn
We post updates on new tools, context engineering patterns, and LLM cost research.