A CrewAI Shopping Assistant That Compares eBay and Amazon

Build a CrewAI crew that searches Amazon and eBay in parallel, then have a comparator agent pick the best real deal across both.

Profile picture of Serply
Serply
Two shopping carts, one labeled Amazon and one labeled eBay, feeding into a single comparison agent

Price comparison shopping sounds like a solved problem until you actually try to automate it. Amazon and eBay don’t expose a shared schema, they don’t agree on what “condition” means, and neither of them wants you scraping their search results pages directly. If you’ve ever tried to hand-roll this with requests and BeautifulSoup, you already know how quickly it turns into a maintenance job instead of a feature.

CrewAI gives you a clean way to model this as a team instead of a script: one agent that knows Amazon, one that knows eBay, and one whose entire job is to look at both result sets and make a call. In this post we’ll build exactly that — a three-agent crew that takes a plain-English shopping request (“a used 15-inch laptop under $800, buy it now”), searches both marketplaces through Serply’s search API, and returns a single recommendation with reasoning.

Why marketplace search needs a “comparator,” not just two tools

If you’ve built agent tools before, the tempting shortcut is to give one agent both an Amazon tool and an eBay tool and let it figure out the comparison itself. That works occasionally, but general-purpose reasoning models are inconsistent at holding two differently-shaped JSON payloads in their head at once and doing arithmetic across them — they’ll happily compare an eBay “was_price” string like "$27.00" against an Amazon numeric price field without normalizing either one first.

Splitting the work into three roles fixes that:

  • amazon_researcher — calls Serply’s Amazon endpoint, returns a short list of normalized candidates.
  • ebay_researcher — calls Serply’s eBay endpoint with the right filters (price range, condition, Buy It Now), returns its own normalized list.
  • comparator — never touches the network. It only reasons over the two lists it’s handed and produces a recommendation.

This is the same reason you wouldn’t have one engineer write the frontend, backend, and QA report simultaneously — separating “go get data” from “make a judgment call” produces more reliable output, and it’s much easier to debug when something goes wrong, because you can inspect each agent’s output independently.

The two data sources

Both tools hit Serply’s REST API, which packs the query string into the URL path rather than a normal ?key=value query string, and authenticates with an X-Api-Key header.

Amazon: /v1/product/search/{query}

GET https://api.serply.io/v1/product/search/q=15+inch+laptop
X-Api-Key: YOUR_API_KEY

The response looks like this:

{
  "products": [
    {
      "link": "https://www.amazon.com/dp/B09LPN7C8Z",
      "asin": "B09LPN7C8Z",
      "title": "15.6 inch Laptop, 8GB RAM, 256GB SSD",
      "price": 649.99,
      "real_position": 3,
      "img_url": "https://m.media-amazon.com/images/I/...jpg",
      "rating_stars": "4.3 out of 5 stars",
      "review_count": 308,
      "extras": ["FREE Shipping", "Only 6 left in stock"],
      "bestseller": false,
      "prime": true,
      "is_sponsor": false
    }
  ],
  "ads": [],
  "ts": 2.07,
  "device_type": null
}

price is already a plain number, which makes the comparator’s job easier. Full field reference is in the Google Product docs.

eBay: /v1/ebay/search/{query}

eBay’s raw filter parameters (_udlo, LH_BIN, LH_ItemCondition, and so on) are not something you want an LLM agent constructing from scratch, so Serply’s eBay endpoint accepts friendly aliases that get translated automatically:

AliasMeaningExample
min_price / max_priceprice range in USDmin_price=200&max_price=800
buy_nowBuy It Now onlybuy_now=1
auctionAuction listings onlyauction=1
conditionnew, open_box, manufacturer_refurbished, seller_refurbished, used, for_partscondition=used
sortbest_match, price_asc, price_desc, newest, ending_soonest, distance_nearestsort=price_asc
free_shippingFree shipping onlyfree_shipping=1

So “a used laptop under $800, Buy It Now, cheapest first” becomes:

GET https://api.serply.io/v1/ebay/search/q=laptop&max_price=800&condition=used&buy_now=1&sort=price_asc
X-Api-Key: YOUR_API_KEY

The response shape is different from Amazon’s — results live under results, not products, and price is a display string rather than a number:

{
  "results": [
    {
      "title": "Dell Latitude 15.6\" Laptop, 8GB RAM, 256GB SSD - Used",
      "link": "https://www.ebay.com/itm/...",
      "position": 1,
      "result_type": "organic",
      "metadata": {
        "price": "$289.99",
        "condition": "Pre-Owned",
        "image": "https://i.ebayimg.com/images/...webp",
        "seller": "refurb_deals_us",
        "seller_feedback": "99.2% positive (4.1K)",
        "attributes": ["Buy It Now", "Free shipping", "Located in United States"]
      }
    }
  ],
  "total": 0,
  "query": "laptop",
  "ts": 1.9,
  "device_region": "",
  "device_type": null
}

One thing worth flagging explicitly, because it’ll bite you if you don’t know about it: total is currently unreliable and always comes back 0 regardless of how many listings actually matched. Don’t build any logic — or agent prompt — that trusts it. Use len(results) instead. Full reference is in the eBay Search docs.

Building the tools

CrewAI tools are just Python callables (or BaseTool subclasses) that an agent can invoke. Here’s the Amazon tool, which normalizes the response into a flat, comparator-friendly shape:

import requests
from crewai.tools import tool

SERPLY_API_KEY = "YOUR_API_KEY"

@tool("amazon_search")
def amazon_search(query: str) -> list[dict]:
    """Search Amazon via Serply and return normalized product candidates."""
    url = f"https://api.serply.io/v1/product/search/q={query.replace(' ', '+')}"
    resp = requests.get(url, headers={"X-Api-Key": SERPLY_API_KEY})
    resp.raise_for_status()
    products = resp.json().get("products", [])

    return [
        {
            "marketplace": "amazon",
            "title": p["title"],
            "price": p["price"],
            "link": p["link"],
            "prime": p.get("prime", False),
            "rating": p.get("rating_stars"),
            "review_count": p.get("review_count"),
        }
        for p in products[:10]
    ]

And the eBay tool, which builds the filter string and normalizes eBay’s string prices into floats so the comparator can do real arithmetic:

import re
import requests
from crewai.tools import tool

SERPLY_API_KEY = "YOUR_API_KEY"

def _price_to_float(price_str: str) -> float | None:
    match = re.search(r"[\d,]+\.\d{2}", price_str or "")
    return float(match.group().replace(",", "")) if match else None

@tool("ebay_search")
def ebay_search(query: str, max_price: float | None = None, condition: str = "used", buy_now: bool = True) -> list[dict]:
    """Search eBay via Serply with price, condition, and Buy It Now filters."""
    parts = [f"q={query.replace(' ', '+')}"]
    if max_price:
        parts.append(f"max_price={max_price}")
    parts.append(f"condition={condition}")
    if buy_now:
        parts.append("buy_now=1")
    parts.append("sort=price_asc")

    url = f"https://api.serply.io/v1/ebay/search/{'&'.join(parts)}"
    resp = requests.get(url, headers={"X-Api-Key": SERPLY_API_KEY})
    resp.raise_for_status()
    results = resp.json().get("results", [])

    return [
        {
            "marketplace": "ebay",
            "title": r["title"],
            "price": _price_to_float(r["metadata"].get("price")),
            "link": r["link"],
            "condition": r["metadata"].get("condition"),
            "seller_feedback": r["metadata"].get("seller_feedback"),
        }
        for r in results[:10]
        if _price_to_float(r["metadata"].get("price")) is not None
    ]

Notice both tools return the same normalized shape (marketplace, title, price, link, plus source-specific extras). That’s the detail that makes the comparator agent’s job tractable — it’s not reconciling two different schemas, just merging two lists of the same shape.

Wiring the crew

from crewai import Agent, Task, Crew, Process

amazon_researcher = Agent(
    role="Amazon Product Researcher",
    goal="Find the best-matching, best-value products on Amazon for a given request",
    backstory=(
        "You're a meticulous online shopper who only trusts Amazon's own listing "
        "data — not marketing copy. You always prefer Prime-eligible, well-reviewed items."
    ),
    tools=[amazon_search],
    verbose=True,
)

ebay_researcher = Agent(
    role="eBay Marketplace Researcher",
    goal="Find well-priced eBay listings that match the buyer's condition and budget requirements",
    backstory=(
        "You're an eBay power user who knows how to filter out sketchy sellers — "
        "you weight seller feedback scores heavily and flag anything with thin feedback history."
    ),
    tools=[ebay_search],
    verbose=True,
)

comparator = Agent(
    role="Deal Comparator",
    goal="Pick the single best overall deal across Amazon and eBay results and justify the choice",
    backstory=(
        "You're a skeptical negotiator. You don't just chase the lowest number — you weigh "
        "price against seller trust, condition, and shipping, and you say so explicitly."
    ),
    tools=[],
    verbose=True,
)

amazon_task = Task(
    description="Search Amazon for: {shopping_request}. Return the top candidates as a normalized list.",
    expected_output="A list of Amazon product candidates with title, price, link, prime, rating.",
    agent=amazon_researcher,
)

ebay_task = Task(
    description=(
        "Search eBay for: {shopping_request}. Apply the buyer's stated max price and condition "
        "as filters, and prefer Buy It Now listings unless the buyer asked for auctions."
    ),
    expected_output="A list of eBay listing candidates with title, price, link, condition, seller_feedback.",
    agent=ebay_researcher,
)

compare_task = Task(
    description=(
        "Given the Amazon and eBay candidate lists, recommend exactly one listing. "
        "Explain the tradeoff in 2-3 sentences: why this one beats the runner-up on "
        "price, trust, or condition."
    ),
    expected_output="A single recommended listing with a link and a short justification.",
    agent=comparator,
    context=[amazon_task, ebay_task],
)

crew = Crew(
    agents=[amazon_researcher, ebay_researcher, comparator],
    tasks=[amazon_task, ebay_task, compare_task],
    process=Process.sequential,
)

result = crew.kickoff(inputs={
    "shopping_request": "a used 15-inch laptop under $800, Buy It Now only"
})

print(result)

The context=[amazon_task, ebay_task] line is what makes this work — CrewAI feeds the comparator agent both prior tasks’ outputs automatically, so it never has to call a search tool itself. It just reasons over data it’s already been handed.

A sample run

Given the request above, a real run against these two tools might return something like:

Recommendation: eBay listing — “Dell Latitude 15.6” Laptop, 8GB RAM, 256GB SSD - Used” — $289.99 (seller feedback: 99.2% positive, 4.1K ratings)

Why: The Amazon candidates in this price range topped out around $649 with 4.3-star ratings, all new. This eBay listing is Buy-It-Now, from a highly-rated seller (4.1K feedback), and comes in at less than half the Amazon price for comparable specs. If new-in-box matters more than price, the Amazon Prime listing is the safer fallback.

That last sentence — an explicit fallback recommendation — is exactly the kind of nuance you lose if you skip the dedicated comparator role and just ask one agent to “search both and pick the best one.”

Extending it further

A few natural next steps once the basic crew is working:

  • Add a budget-enforcement guardrail in the comparator’s task description so it refuses to recommend anything over the buyer’s stated max price, even if it’s the “best” option by other measures.
  • Cache search results for a few minutes if your crew runs on a schedule — repeated identical searches within a short window don’t need to hit the API again.
  • Swap in eBay’s other filters — local pickup, free returns, seller restriction — for more specialized shopping agents (e.g., a “local pickup only” crew for large furniture).

Both endpoints used here need an API key — see the Authentication guide for how to get one, and the full Google Product and eBay Search docs for every field these tools can return.