ChatGPT query fan-out
The web searches ChatGPT ran to build an answer, and the keywords behind its citations.
ChatGPT searches the web before it answers. `searchQueries` lists those searches, which explains why a page was cited and maps each prompt to the keywords behind it. The ChatGPT answers served today do not carry them, so the field comes back empty and costs nothing.
Returned in result
| Field |
|---|
searchQueries |
Request
curl -X POST "https://api.answerline.dev/v1/monitor/chatgpt" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt":"What are the best running shoes for flat feet?","country":"US","include":{"markdown":true}}'
What ChatGPT’s query fan-out is
Query fan-out is the set of web searches ChatGPT runs to build an answer. The searches need not match the prompt as typed: one prompt can become several rewritten searches, and the pages it cites come from what those searches return. The API has a field for them, result.searchQueries, a list of strings. The ChatGPT answers served today do not carry their searches, so the list comes back empty.
What the answers do carry is the result of those searches: sources, the cited pages in order, and citationPills, which ties each sentence to the page it cites. They show which pages won, though not the queries they won on.
The query fan-out post introduces the idea. This page covers the field and its request options.
Field reference
| Field | Type | Meaning |
|---|---|---|
result.searchQueries |
string[] | The searches ChatGPT ran to generate the response. Present when include.searchQueries is true; empty on the ChatGPT answers served today. |
result.model |
string, nullable | The ChatGPT model used. Can be empty. |
result.inlineProducts[].product.generatedProductQuery |
string | Only with include.shopping: the broad shopping query ChatGPT generated for one product. In the contract, but not filled on the answers served today; inlineProducts itself holds the shopping-search products. |
Two request options relate to fan-out. Neither fills searchQueries on the ChatGPT answers served today:
| Option | Default | Effect |
|---|---|---|
legacy |
false |
Accepted for compatibility; it does not change the answer today. |
disableWebSearch |
false |
Accepted; it changes nothing today. ChatGPT decides on its own whether to search. |
Annotated excerpt
Values are illustrative; field names follow the POST /v1/monitor/chatgpt result schema with include.searchQueries set to true.
{
"success": true,
"result": {
"text": "For a team under 20 people, look for a help desk with shared inboxes and...",
// The field exists; it comes back empty on the ChatGPT answers served today.
"searchQueries": [],
"model": null,
"sources": [
{ "position": 1, "url": "https://www.example-reviews.com/help-desk-small-teams", "label": "Help desks for small teams", "footnote": false }
]
}
}
Cost and request options
include.searchQueries costs nothing extra: a request stays at the $0.0006 ChatGPT base (Starter tier). The ChatGPT answers served today do not carry the searches behind them, so searchQueries currently comes back empty; the flag is kept so requests that set it keep working. legacy and disableWebSearch do not change the price.
Code for an empty list: an empty searchQueries means the answer did not carry its searches, not that ChatGPT ran none.
Example: counting fan-out queries
The script shows how the field is read: it samples each prompt, then reports average searches per answer, the most frequent queries and terms, and which queries overlap your tracked keyword list. On the ChatGPT answers served today it reports 0 answers with fan-out, because searchQueries comes back empty.
import os
import re
import time
from collections import Counter
from datetime import date
import requests
API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
PROMPTS = ["best help desk software for a small team", "cheapest help desk with a shared inbox"]
SAMPLES = 5
DAY = date.today().isoformat()
TRACKED = {"help desk software", "shared inbox software", "help desk pricing"}
STOP = {"the", "a", "for", "and", "of", "vs", "in", "to", "with", "best"}
def run_tasks(tasks):
resp = requests.post(f"{API}/v1/async/task/batch", headers=HEADERS, json=tasks, timeout=60)
resp.raise_for_status()
pending = {item["task"]["id"] for item in resp.json()["results"] if item["success"]}
results = []
while pending:
time.sleep(10)
for task_id in list(pending):
body = requests.get(f"{API}/v1/async/task/{task_id}", headers=HEADERS, timeout=30).json()
status = body["task"]["status"]
if status in ("COMPLETED", "FAILED"):
pending.discard(task_id)
if status == "COMPLETED":
results.append(body["response"]["result"])
return results
def normalize(query):
return " ".join(re.sub(r"\b20\d\d\b", "", query.lower()).split())
tasks = [
{
"taskType": "CHATGPT",
"payload": {"prompt": prompt, "country": "US", "include": {"searchQueries": True}},
"idempotencyKey": f"helpdesk-fanout-{p}-s{s}-{DAY}",
}
for p, prompt in enumerate(PROMPTS)
for s in range(SAMPLES)
]
answers = run_tasks(tasks)
with_queries = [r for r in answers if r.get("searchQueries")]
queries = Counter()
terms = Counter()
for result in with_queries:
seen = {normalize(q) for q in result["searchQueries"]}
queries.update(seen)
terms.update({t for q in seen for t in q.split() if t not in STOP})
searches = sum(len(r["searchQueries"]) for r in with_queries)
print(f"{len(with_queries)} of {len(answers)} answers returned fan-out")
print(f"average searches per answer: {searches / max(len(with_queries), 1):.1f}")
for query, n in queries.most_common(15):
flag = "tracked" if any(k in query for k in TRACKED) else "new"
print(f"{n:3} answers [{flag:7}] {query}")
print("top terms:", ", ".join(t for t, _ in terms.most_common(20)))
Each query is counted once per answer, so the count reads as “ran in N answers” and repeats inside one answer don’t inflate it. Years are stripped before counting so “best help desk 2026” and “best help desk” group together; keep them if the year is what you are studying.
Pitfalls
Every ChatGPT answer served today comes back with an empty searchQueries. Do not count an empty list as zero searches in a report; report the count of answers that returned queries next to any average.
generatedProductQuery describes one product’s shopping lookup, not the answer’s web searches. Report it separately from searchQueries.
Related
- ChatGPT engine guide: sync and async calls, webhooks, geo targeting and billing.
- ChatGPT sources for the pages those searches led to, and ChatGPT shopping for product cards.
- Keyword research and People Also Ask keyword research.
- Search-side views of the same topic: related searches and People Also Ask.
- GEO vs SEO metrics.
Questions
How do I get ChatGPT's query fan-out from the API?
The field is result.searchQueries, an array of strings, requested with include.searchQueries. The ChatGPT answers served today do not carry their searches, so the array comes back empty and the API does not return ChatGPT's fan-out.
How much does include.searchQueries cost?
Nothing extra: a ChatGPT request stays at $0.0006 at the Starter tier. The ChatGPT answers served today do not carry their searches, so the field comes back empty, and a flag that returns nothing is not charged.
Why is searchQueries empty?
The ChatGPT answers served today do not carry the searches behind them, so searchQueries comes back empty on every answer. Setting legacy or disableWebSearch does not change that.
What is the difference between searchQueries and generatedProductQuery?
searchQueries lists the web searches behind the whole answer. generatedProductQuery is defined on an inline product for the broad shopping query ChatGPT generated for that one product. inlineProducts holds the shopping-search products on the answers served today, but generatedProductQuery is not filled on them.
What can I use for keyword work instead?
The fields ChatGPT answers do carry: sources lists the cited pages in order, citationPills shows which sentence cites which page, and text holds the brand and product names the answer uses. Check whether your pages rank for the topics those cited pages cover.
More ChatGPT data
sources
Every page ChatGPT cited in its answer, in order, with the inline chips that group them.
shopping results
The products ChatGPT's shopping search shows, with their prices and offers, in inlineProducts; shoppingCards is usually empty today.
entities
The products, brands and other named things in a ChatGPT answer, as structured entities.