How to Build a Shopping Agent With ATXP
Most tutorials on how to build a shopping agent ai stop at product search. They show you how to call a search API, maybe parse some results, and leave you to figure out the rest. What they skip is everything that makes a shopping agent actually work in production: budget enforcement, purchase authorization, receipt generation, and graceful handling when a limit is hit mid-run.
This guide covers the full commerce loop. By the end, you’ll have a working shopping agent that can search for products, compare prices, authorize purchases within a per-task budget, and return a receipt — all through ATXP, without managing separate API keys or billing accounts for each service.
What Does a Shopping Agent Actually Need to Do?
A shopping agent has five distinct jobs, and most agents only do the first two:
- Search — find products matching user criteria
- Compare — evaluate options against price, availability, rating, shipping
- Authorize — confirm the purchase fits within budget before committing
- Execute — complete the transaction
- Receipt — return structured proof of what was purchased and at what cost
Steps 3–5 are where agent payments infrastructure matters. Without spending enforcement at step 3, your agent will buy whatever it finds. Without a proper receipt at step 5, you have no audit trail — and no way to debug why your agent spent $47 when you expected $12.
ATXP handles steps 3–5 natively. You handle the domain logic for 1–2.
Architecture Overview
The shopping agent in this guide follows a linear pipeline:
user task → search tool → compare tool → budget check → purchase tool → receipt tool
Each step is a tool call. ATXP sits between the compare and purchase steps, enforcing a per-task credit allocation before any money moves.
Components:
- LangChain as the agent framework (swap CrewAI or the OpenAI Agents SDK if preferred — the ATXP calls are identical)
- ATXP for payment authorization, credit management, and receipts
- A product search API (SerpAPI, Oxylabs, or any price-comparison endpoint)
- A checkout API (Stripe, or a mock for testing)
Step 1: Set Up ATXP
If you haven’t connected an agent to ATXP yet, start here. The setup is: create an agent account, get a credential, set your spending limits.
import os
from atxp import ATXPClient
client = ATXPClient(
agent_id=os.environ["ATXP_AGENT_ID"],
credential=os.environ["ATXP_CREDENTIAL"]
)
Each agent gets its own credential. The agent can only spend what you’ve allocated — it cannot access your underlying payment method directly. This is the key security property: per-task budget isolation rather than a shared card that any agent task can drain.
Step 2: Define the Per-Task Budget
Before the agent starts a shopping task, allocate a credit budget for that specific run. When the budget is exhausted, the agent stops — it cannot overspend.
def create_shopping_task(user_request: str, max_spend_usd: float) -> dict:
task = client.tasks.create(
description=user_request,
budget_usd=max_spend_usd,
currency="USD",
expiry_minutes=30 # task expires if not completed
)
return {"task_id": task.id, "budget_remaining": task.budget_remaining_usd}
This is not a soft limit. Once the credit allocation is consumed, subsequent purchase() calls return an INSUFFICIENT_CREDITS error. The agent’s error handler decides what to do — return partial results, ask the user for more budget, or abort cleanly.
Step 3: Build the Tool Set
Define four tools: search, compare, purchase, and get_receipt. The agent will call these in sequence.
from langchain.tools import tool
@tool
def search_products(query: str, max_price: float) -> list[dict]:
"""Search for products matching a query under a price ceiling."""
results = search_api.search(query=query, max_price=max_price, limit=10)
return [
{
"id": r.id,
"title": r.title,
"price": r.price,
"seller": r.seller,
"rating": r.rating,
"in_stock": r.in_stock,
"url": r.url
}
for r in results if r.in_stock
]
@tool
def compare_products(products: list[dict], criteria: str) -> dict:
"""Rank products by criteria (price, rating, shipping). Return the best match."""
# Domain logic: score products by the stated criteria
scored = rank_by_criteria(products, criteria)
return scored[0] # best match
@tool
def purchase_product(task_id: str, product_id: str, price: float, seller: str) -> dict:
"""Authorize and execute a purchase through ATXP. Returns transaction ID or error."""
try:
txn = client.tasks.purchase(
task_id=task_id,
amount_usd=price,
merchant=seller,
metadata={"product_id": product_id}
)
return {"status": "success", "transaction_id": txn.id, "amount_charged": txn.amount}
except ATXPInsufficientCreditsError:
return {"status": "error", "reason": "INSUFFICIENT_CREDITS", "price": price}
except ATXPMerchantNotAllowedError:
return {"status": "error", "reason": "MERCHANT_NOT_IN_ALLOWLIST", "seller": seller}
@tool
def get_receipt(transaction_id: str) -> dict:
"""Retrieve a structured receipt for a completed transaction."""
receipt = client.receipts.get(transaction_id)
return {
"transaction_id": receipt.id,
"merchant": receipt.merchant,
"amount": receipt.amount_usd,
"timestamp": receipt.completed_at,
"task_id": receipt.task_id,
"product_metadata": receipt.metadata
}
Step 4: Wire the Agent
from langchain.agents import initialize_agent, AgentType
from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [search_products, compare_products, purchase_product, get_receipt]
agent = initialize_agent(
tools=tools,
llm=llm,
agent=AgentType.OPENAI_FUNCTIONS,
verbose=True
)
def run_shopping_agent(user_request: str, max_spend: float) -> dict:
task = create_shopping_task(user_request, max_spend)
prompt = f"""
Task ID: {task['task_id']}
Budget: ${task['budget_remaining']} USD
User request: {user_request}
Use the available tools to:
1. Search for products matching the request
2. Compare results and identify the best match
3. Purchase the best match if it fits within the budget
4. Return the receipt
If the best match exceeds the budget, return the top option with its price
and explain that authorization was not possible within the allocated budget.
"""
result = agent.run(prompt)
return result
What the Commerce Loop Looks Like at Runtime
Here’s a sample trace for the request “buy a USB-C hub under $40, best rated”:
→ search_products("USB-C hub", max_price=40.0)
← 8 results returned, 6 in stock
→ compare_products([...], criteria="rating then price")
← best match: Anker 7-in-1 Hub, $34.99, rating 4.7, seller: Anker Store
→ purchase_product(task_id="t_abc123", product_id="B08...", price=34.99, seller="Anker Store")
← {"status": "success", "transaction_id": "txn_xyz789", "amount_charged": 34.99}
→ get_receipt("txn_xyz789")
← {merchant: "Anker Store", amount: 34.99, timestamp: "2026-03-30T14:22:01Z", ...}
Total task runtime: ~8 seconds. Budget consumed: $34.99 of $40.00 allocated. $5.01 returned to the agent pool.
How Does Per-Task Budget Enforcement Actually Work?
This is the part most agent payment guides skip. ATXP’s credit model works like this:
- You allocate credits to a task at creation (
budget_usd=40.0) - Each
purchase()call within the task debits from that allocation - The allocation is isolated — other tasks cannot draw from it
- When the task expires or completes, unused credits return to the agent’s pool
The result: a runaway subtask cannot drain your agent’s entire balance. One bad shopping loop that calls purchase() 50 times will hit the task limit, not your account limit. This is the same isolation property that makes IOU tokens safer than virtual cards for agent workloads.
Comparison: Shopping Agent with and Without Payment Infrastructure
| Capability | DIY (raw API keys + card) | ATXP |
|---|---|---|
| Per-task spend isolation | Manual, error-prone | Native — task budget required |
| Merchant allowlisting | Not available | Configurable per agent |
| Audit trail | None by default | Structured receipt on every txn |
| Overspend protection | Card limit only (account-wide) | Task-level hard cap |
| Credential surface | One card exposed to all agents | Isolated credential per agent |
| Error handling on decline | Card network error (opaque) | Typed errors: INSUFFICIENT_CREDITS, MERCHANT_NOT_ALLOWED |
| Receipt format | Whatever the merchant returns | Normalized across all merchants |
The normalized receipt is underrated. When your agent makes purchases across five different merchants, getting a consistent schema back means your logging and audit code doesn’t need to handle five different response formats.
Handling Edge Cases
What if the agent’s search returns nothing?
The agent should return a clear no-results message without calling purchase_product. Define this in the prompt or as a guardrail in your agent loop.
What if the best match is over budget?
The purchase_product tool handles this explicitly — it returns INSUFFICIENT_CREDITS with the attempted price rather than throwing. The agent’s LLM can then decide whether to return the result to the user with an explanation or search for a cheaper alternative.
What if the merchant isn’t in the allowlist?
MERCHANT_NOT_ALLOWED is returned. Add merchants to the agent’s allowlist in the ATXP dashboard. This is intentional — agents shouldn’t be able to buy from arbitrary merchants without explicit authorization. Your audit trail will show every attempted merchant, including rejected ones.
What if the task expires mid-run?
ATXP returns a TASK_EXPIRED error on any subsequent purchase() call. Handle this the same way as INSUFFICIENT_CREDITS — surface it to the user rather than silently failing.
If you’re building a shopping agent and want to skip the payment infrastructure setup, ATXP handles the authorization layer — your agent connects once and can transact across every merchant in the network without managing separate billing accounts.
Production Considerations
A few things that matter at scale that don’t show up in tutorials:
Receipt storage. ATXP stores receipts server-side. You can retrieve any receipt by transaction_id at any point. Build a local cache if you need fast lookup, but don’t treat your local store as authoritative.
Concurrent tasks. If you’re running multiple shopping agents simultaneously, each gets its own task allocation. They don’t share a pool unless you’ve configured a shared agent account — which you generally shouldn’t do for isolated workloads.
Task timeouts. Set expiry_minutes conservatively. A 30-minute shopping task that never completes ties up credits. Most shopping tasks complete in under 2 minutes. Set timeouts accordingly and handle the expiry error path.
Merchant coverage. ATXP’s merchant network covers major e-commerce APIs. If you need a specific merchant added, contact support — the allowlist is configurable per agent.
Extending This Pattern
The shopping agent pattern extends directly to other purchase workflows:
- Travel booking: same pipeline, higher-value tasks, per-segment budget enforcement (flight + hotel + car as separate task allocations)
- SaaS procurement: agent buys API credits for downstream services — same
purchase_productcall, merchant is the API provider - Marketplace bidding: real-time price queries with budget cap enforcement per auction item
The receipt schema and budget isolation model are the same across all of these. Once you’ve built the shopping agent, the rest of the commerce patterns follow the same structure.
FAQ
Can the shopping agent make purchases without human approval?
Yes — that’s the point of autonomous commerce. Budget enforcement is the approval mechanism: you set the ceiling when you create the task, and the agent operates within it without requiring per-transaction sign-off. If you want human-in-the-loop approval for purchases above a certain amount, add that check in your purchase_product tool before calling client.tasks.purchase().
Does ATXP support international purchases? ATXP credits are USD-denominated at allocation time. Merchant settlements in other currencies are handled at the ATXP layer — the agent always sees a USD debit against its task budget. Currency conversion is not something the agent code needs to handle.
What frameworks does this work with?
The ATXP client is framework-agnostic Python. The example uses LangChain’s tool interface, but the ATXPClient calls are identical in CrewAI, the OpenAI Agents SDK, or any framework that supports callable tools. The task/purchase/receipt pattern doesn’t change.
How do I test this without making real purchases?
ATXP has a sandbox mode. Set ATXP_ENV=sandbox — all purchase() calls are simulated, receipts are generated, and budget deductions are tracked, but no real transactions occur. Sandbox credentials are available in the ATXP dashboard.
What happens to unspent budget when a task completes?
Unused credits return to the agent pool automatically when the task completes or expires. There’s no manual release step. The agent pool balance is updated in real time — you can query it via client.agent.balance().
Summary
A working shopping agent needs five things: search, comparison, budget enforcement, purchase execution, and receipt generation. Most tutorials cover the first two. ATXP handles the last three.
The full pattern: allocate a per-task budget before the agent runs, define typed error handling for budget and merchant failures, and retrieve a normalized receipt at the end. Everything else is domain logic — your search queries, your comparison criteria, your product taxonomy.
The code in this guide is production-ready. Swap in your search API, set your merchant allowlist, and the commerce loop works.
For the next step, see how to build a research agent with ATXP — the same budget isolation pattern applies to any agent that makes external API calls with cost implications, not just purchases.