Home / Blog / OpenAI Function Calling with Live Amazon Product Data

OpenAI Function Calling with Live Amazon Product Data

Give an OpenAI model a tool that looks up real Amazon prices, ranks and titles: the Chat Completions tools/tool_calls contract, a manual tool-calling loop with a hard credit budget, and the same guardrails as the Claude tool-calling guide, adapted to OpenAI's API shape.

October 24, 2026
4 min read
SellerMagnet Team
Share & Bookmark
Code editor showing an OpenAI tool-calling loop returning live Amazon price and rank data

OpenAI's Chat Completions API reaches the SellerMagnet API through function calling: describe a tool once in the tools parameter, and while the model keeps returning finish_reason: "tool_calls", your code runs the requested function and sends the result back as a role: "tool" message tagged with the call's id. The model never sees the API key or makes the HTTP request itself; your code does both, and only your code decides when to stop.

Key Takeaways

  • finish_reason: "tool_calls" with a non-empty message.tool_calls array means the model wants one or more functions run before it continues.
  • Each tool result goes back as its own {"role": "tool", "tool_call_id": ..., "content": ...} message, matched to its call by id, not by order.
  • tool_choice: "none" forces a final answer without another tool call, which is how a credit budget gets enforced in code rather than hoped for in the prompt.
  • Validate the ASIN locally before calling the tool function's body; the lookup endpoint bills a malformed ASIN like any other request.
  • Send errors back as JSON content on the tool message, not an exception that stops the loop, so the model can recover or explain the problem.

How do I describe the Amazon lookup tool to the model?

A tools entry is a type: "function" object with a JSON Schema parameters block. Marketplace IDs are an enum, not a free string, so the model cannot invent or misspell one; the ASIN itself stays a plain string, checked locally once the model calls the function.

tools.py: the tool schema and the function it describes

"""The Amazon product lookup tool, offered to the model via OpenAI's function-calling
schema. Reuses the quickstart's sellermagnet.py / retry.py for the real API call
(https://sellermagnet-api.com/blog/amazon-product-api-python).
"""
import json
import re

from retry import get_with_retry
from sellermagnet import ApiError, new_session

ASIN_RE = re.compile(r"^[A-Z0-9]{10}$")
MARKETPLACES = ["ATVPDKIKX0DER", "A1PA6795UKMFR9", "A13V1IB3VIYZZH", "A1F83G8C2ARO7P", "APJ6JRA9NG5V4"]

LOOKUP_TOOL = {
    "type": "function",
    "function": {
        "name": "amazon_product_lookup",
        "description": "Look up the current title, Buy Box price and bestseller rank for one Amazon ASIN.",
        "parameters": {
            "type": "object",
            "properties": {
                "asin": {"type": "string", "description": "10-character Amazon ASIN, e.g. B0CL61F39H"},
                "marketplace_id": {"type": "string", "enum": MARKETPLACES,
                                   "description": "Amazon marketplace ID; defaults to amazon.com"},
            },
            "required": ["asin"],
        },
    },
}

session = new_session()


def amazon_product_lookup(asin: str, marketplace_id: str = "ATVPDKIKX0DER") -> str:
    if not ASIN_RE.match(asin):
        return json.dumps({"error": f"'{asin}' is not a 10-character ASIN; check it before calling the tool"})
    try:
        data = get_with_retry(session, "amazon-product-lookup", asin=asin, marketplaceId=marketplace_id)
    except ApiError as err:
        return json.dumps({"error": f"lookup failed ({err.status}): {err}"})
    p = data["productInfo"]
    box = p.get("buyBoxInfo") or {}
    price = box.get("price")
    rank = (p.get("bestsellerRanks") or {}).get("main_category") or {}
    return json.dumps({  # a handful of fields, not the whole listing - this gets resent as input on every later turn
        "asin": asin, "marketplace_id": marketplace_id, "title": p.get("title"),
        "buy_box_price": price if isinstance(price, (int, float)) else None, "currency": box.get("currencyCode"),
        "rank": rank.get("rank"), "rank_category": rank.get("name"),
    })

How does the tool-calling loop work in Python?

Call the model with the tool, and while it keeps asking for one, run every requested call and append each result as its own tool message before calling the model again. The loop ends on any finish_reason other than tool_calls. Once the budget is spent, tool_choice: "none" forces a final answer instead of another round. Status codes, retries and Retry-After for the underlying SellerMagnet calls are covered in rate limits and error handling; the same tool-contract pattern for Claude is in the AI agent tool-calling guide.

agent.py: a manual tool-calling loop with a hard budget

"""A manual OpenAI tool-calling loop with a hard tool-call budget."""
import json

from openai import OpenAI

from tools import LOOKUP_TOOL, amazon_product_lookup

client = OpenAI()
MAX_TOOL_CALLS = 10  # one credit each: the most a single question may spend
SYSTEM = (
    "You answer questions about Amazon products. Use amazon_product_lookup for any "
    "current price, rank or title - never quote them from memory. Always name the "
    "ASIN and marketplace your answer is based on."
)


def ask(question: str) -> str:
    messages = [{"role": "system", "content": SYSTEM}, {"role": "user", "content": question}]
    calls = 0
    while True:
        response = client.chat.completions.create(
            model="gpt-5",
            messages=messages,
            tools=[LOOKUP_TOOL],
            # once the budget is spent, force a final answer instead of more tool calls
            tool_choice="none" if calls >= MAX_TOOL_CALLS else "auto",
        )
        choice = response.choices[0]
        message = choice.message
        if choice.finish_reason != "tool_calls" or not message.tool_calls:
            return message.content or ""

        messages.append({"role": "assistant", "content": message.content, "tool_calls": message.tool_calls})
        for call in message.tool_calls:
            calls += 1
            try:
                args = json.loads(call.function.arguments)
            except json.JSONDecodeError:
                content = json.dumps({"error": "arguments were not valid JSON"})
            else:
                if calls > MAX_TOOL_CALLS:
                    content = json.dumps({"error": "Tool budget used up. Answer with what you have."})
                else:
                    try:
                        content = amazon_product_lookup(**args)
                    except Exception as exc:
                        content = json.dumps({"error": str(exc)})
            # each tool result goes back as its own role:"tool" message, tagged with the call's id
            messages.append({"role": "tool", "tool_call_id": call.id, "content": content})


if __name__ == "__main__":
    question = "What does B0CL61F39H cost on amazon.com right now, and how does it rank?"
    print(f"Q: {question}")
    print(f"A: {ask(question)}")

Example run, from python agent.py (illustrative data)

Q: What does B0CL61F39H cost on amazon.com right now, and how does it rank?
A: B0CL61F39H costs $444.99 on amazon.com (ATVPDKIKX0DER) and ranks #31 in Video Games.
Sequence of the tool-calling loop between your code, the model and the lookup tool
Your code runs the call; the loop ends when the model's finish_reason is stop.

What the model receives as the tool result content

{
  "asin": "B0CL61F39H",
  "marketplace_id": "ATVPDKIKX0DER",
  "title": "PlayStation5 console (slim)",
  "buy_box_price": 444.99,
  "currency": "USD",
  "rank": 31,
  "rank_category": "Video Games"
}

Which guardrails keep an agent cheap and honest?

An agent spends credits and tokens on its own initiative, so the limits belong in code, not the prompt. Each guardrail below is a few lines in the example above.

Guardrails for an OpenAI agent calling a paid data API.
GuardrailWhy it mattersWhere it lives
Cap tool calls per questionEach call spends one API creditMAX_TOOL_CALLS in the loop
Trim the tool resultResent as input on every later turn7 fields, not the whole listing
Enum the marketplace IDThe model cannot invent or misspell an IDenum in the parameters schema
Validate the ASIN locallyA malformed ASIN still bills once sentASIN_RE before the request
Return errors as contentThe model can recover instead of guessingAn error field in the tool JSON
Keep the key out of promptsThe model never needs to see itnew_session() reads the env var
Table of six guardrails for an OpenAI agent calling a paid Amazon data API
Six guardrails, all in code: the model can ask for anything, only this is allowed to happen.

parallel_tool_calls can return more than one call at once

By default the model may return several tool_calls in one response. The loop above runs every one of them and sends back one role: "tool" message per call, in the order they arrived; set parallel_tool_calls: false in the request if the budget check needs to see calls one at a time instead of a batch.

Frequently Asked Questions

Does OpenAI call the SellerMagnet API directly?

No. The model only returns the function name and arguments; your own code runs amazon_product_lookup() and sends the JSON result back.

What happens on a malformed ASIN?

The tool function checks the ASIN format locally and returns an error in its JSON content before any billed call is made.

How do I stop an agent from making unlimited tool calls?

Count calls in your own loop and switch tool_choice to "none" once a budget is reached; the API has no built-in call limit.

What if the lookup fails (a 404 or a rejected key)?

Send the error back as the tool message's content instead of raising; the model can then explain the problem or try a different ASIN.

Is this different from the Claude tool-use pattern?

The contract differs in field names (tool_calls vs tool_use blocks, role:tool vs tool_result), but the loop shape (call, run, send results, repeat until done) is the same; see the Claude guide for that version.

Bottom line: one tool schema, a loop that runs every requested call and sends results back tagged by id, and a hard call budget in code turn an OpenAI model into an Amazon product assistant without ever showing it the API key. Every field the tool reads is on the product lookup page.

Ready to Extract Amazon Data at Scale?

Start building with SellerMagnet API today. Real-time product data, competitive pricing, and review analytics at your fingertips.

150 free API credits • No credit card required • Cancel anytime