Quick answer
How do I give an OpenAI model a web search tool?
Declare a google_search function with a JSON Schema in tools, then loop: when the model returns tool_calls, run each search on https://serpcrypto.com/api/v1/search and append the results as tool messages until it answers. SerpCrypto charges 1 credit per successful search, from $0.30/1K searches, paid in crypto with no KYC.
What does the tool schema look like?
A function tool has a name, a description and parameters in JSON Schema. The model sees only these three things, so the description must say what the tool returns and when to use it. Only query is required; country and language are optional and default to US English in the code.
{
"type": "function",
"function": {
"name": "google_search",
"description": "Search Google for current information. Returns the top results with title, link and snippet. Use it for facts newer than your training data.",
"parameters": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "The search query, as a person would type it." },
"gl": { "type": "string", "description": "Two-letter country code of the results, e.g. us, gb, de." },
"hl": { "type": "string", "description": "Language code, e.g. en, de, pt-BR." }
},
"required": ["query"]
}
}
}How does the tool-calling loop work?
Send the question with tools. If the reply contains tool_calls, run each one, append the assistant message and one tool message per call with the matching tool_call_id, then call the model again. When a reply has no tool call, it is the final answer.
The loop below stops after three searches. A cap protects your credits if a model keeps searching, and three searches answer most factual questions.
"""Tool-calling loop: the model asks for searches, this code runs them on SerpCrypto."""
import json
import os
import requests
from openai import OpenAI
client = OpenAI() # reads OPENAI_API_KEY
MODEL = os.environ["OPENAI_MODEL"] # any OpenAI chat model with tool calling
API_URL = "https://serpcrypto.com/api/v1/search"
TOOLS = [
{
"type": "function",
"function": {
"name": "google_search",
"description": "Search Google for current information. Returns the top results with title, link and snippet.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query."},
"gl": {"type": "string", "description": "Two-letter country code, e.g. us."},
"hl": {"type": "string", "description": "Language code, e.g. en."},
},
"required": ["query"],
},
},
}
]
def google_search(query, gl="us", hl="en"):
r = requests.get(
API_URL,
params={"q": query, "gl": gl, "hl": hl},
headers={"Authorization": "Bearer " + os.environ["SERPCRYPTO_API_KEY"]},
timeout=90,
)
if r.status_code != 200:
return f"Search failed with HTTP {r.status_code}."
items = r.json().get("organic_results", [])[:5]
return "\n\n".join(f"{x['title']}\n{x['link']}\n{x.get('snippet', '')}" for x in items) or "No results."
def ask(question, max_searches=3):
messages = [{"role": "user", "content": question}]
for _ in range(max_searches + 1):
reply = client.chat.completions.create(model=MODEL, messages=messages, tools=TOOLS)
msg = reply.choices[0].message
if not msg.tool_calls:
return msg.content
messages.append(msg)
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
messages.append({"role": "tool", "tool_call_id": call.id, "content": google_search(**args)})
return "Stopped: search limit reached."
print(ask("Which countries changed their data protection laws in 2026? Cite sources."))Does the same schema work with Claude and other models?
Yes, with a different wrapper. Claude's tool use takes the same JSON Schema under input_schema, next to name and description. Most other tool-calling APIs also accept JSON Schema for parameters; only the outer field names change. The search function itself stays the same, because it only calls the SerpCrypto endpoint.
{
"name": "google_search",
"description": "Search Google for current information. Returns the top results with title, link and snippet.",
"input_schema": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "The search query." },
"gl": { "type": "string", "description": "Two-letter country code, e.g. us." },
"hl": { "type": "string", "description": "Language code, e.g. en." }
},
"required": ["query"]
}
}What should the tool return to the model?
Short text with links. Five results with title, link and snippet are usually enough for the model to answer and to cite. Returning the full JSON works but costs more tokens and gives the model more noise to read.
Add the AI Overview text when present if you want the model to see Google's own summary, and keep the source links so the answer can cite pages rather than the overview.
How do I keep search costs predictable?
Count tool calls: each one that succeeds is one credit. Cap searches per question, as the loop does, and log each call with its query so you can see what the model asks for. On the 100,000 searches pack the rate is $0.56/1K searches, and Prepaid search credits never expire: unused searches stay on your account until you use them.
Errors cost nothing: 1 credit = 1 successful search request, which returns one page of results on any supported engine. A request that fails is not charged.
Function calling questions
Can I use the Responses API instead of Chat Completions?
Yes. The Responses API takes function tools with the same JSON Schema, with name, description and parameters at the top level of each tool. The search function and the idea of the loop do not change.
Why not use the model provider's built-in web search?
Built-in search tools choose their own sources and settings. A function on SerpCrypto gives you Google results for the country and language you pick, as JSON you can log, at a known price per search.
Can the model choose the country?
Yes. gl is a parameter of the tool, so the model can set it from the question, for example gb for a question about the United Kingdom. Keep a default in your code for questions that name no country.
Do I need a credit card to get the search API key?
No. SerpCrypto credits are paid in BTC, USDT, USDC, ETH, SOL, LTC, with no ID, no selfie and no documents. Account-number login; email is optional.