ChatGPT API
The answer ChatGPT shows its users, with every cited source in order, sentence-level citations, the brands, products and places it names, and its pictures.
Price
$0.60 per 1,000 requests at Starter, down to $0.42 at Max, deducted from your prepaid USD balance.
Endpoint
POST /v1/monitor/chatgpt
Countries
213 countries.
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}}'
Request options
| Field | Type | Description |
|---|---|---|
prompt * |
string |
Prompt submitted to ChatGPT, up to 2,000 characters. A longer prompt is refused with a 400 that states its length. |
country * |
string |
Code of the country or region used to localize the response |
include |
object |
Flags that add extra output formats to the response (optional) |
include.html |
boolean |
A copy of the ChatGPT page as it was when the answer was captured, stored on our file host and linked in `html`, to check the answer against. It costs extra, priced at what the page costs us to load, at the task's own margin. |
include.markdown |
boolean |
Add a markdown version of the response to the result |
include.rawResponse |
boolean |
Return the answer as normalized stream events. For the engine's answer as received, use `include.rawWire`. |
include.searchQueries |
boolean |
Return the fan-out queries ChatGPT issued while producing the response (empty on the answers served today) |
include.ads |
boolean |
Return ads that ChatGPT shows in its response (empty on the answers served today) |
include.shopping |
boolean |
Return shopping cards plus products shown inline (inlineProducts), with their prices and offers. Free. Today the products from ChatGPT's shopping search come in inlineProducts and shoppingCards is usually empty. |
include.rawWire |
boolean |
Return the engine's own response as received in result.rawWire: the answer stream, the rendered results pages or the RSS feed, without Gemini's detected location or ChatGPT's session tokens; null where it was not captured. Free. See https://answerline.dev/docs/output-formats#raw-wire |
include.screenshot |
boolean |
Return a full-page PNG screenshot of the engine's page as it showed the finished answer, in result.screenshot: a link, or one link per results page on Google Search. On ChatGPT, Gemini, AI Mode, Google Search, Perplexity and Grok; other engines refuse it with a 400. Absent when the answer was read without a rendered page or its page was not available. Free. See https://answerline.dev/docs/output-formats#screenshots |
legacy |
boolean |
Accepted for compatibility with clients that ask for ChatGPT's older desktop interface. Not served yet: the answer arrives in the same shape whatever its value. Default: false. |
disableWebSearch |
boolean |
Accepted for compatibility. On every answer served today ChatGPT decides by itself whether to search the web, whatever this value, and `result.provenance.searchMode` records `engine-default`. Default: false. |
state |
string |
State code for targeting below the country level (e.g., "CA"; "DC" included). Allowed only when country is "US". Served for Gemini only; ChatGPT, Copilot, Perplexity and Grok refuse `state` with a 400, uncharged. "DC" fails uncharged: no exit serves it. |
answerDepth |
"any" | "full" | "quick" |
How deep an answer the engine writes, where it offers a choice. any (the default): a full answer when the engine can give one now, else its short one. full: a full answer or a retryable error, never charged. quick: the short one, the fastest. Perplexity offers the choice; every other engine has one depth and ignores the field. provenance.answerDepth says what was served. Same price for every value. See https://answerline.dev/docs/engine-features/#answer-depth |
What you get back
Field in result | Type | Description |
|---|---|---|
text | string | Text of ChatGPT's reply |
markdown | string | ChatGPT's reply as Markdown; returned only if include.markdown is true |
sources | object[] | Sources the response cites; an empty array if there are none. |
searchQueries | string[] | The fan-out queries ChatGPT issued while producing the response (present when include.searchQueries is true) |
shoppingCards | object[] | Shopping/product cards parsed from the response. When `include.shopping` is `false` or not set, `result` does not contain this field at all, so treat it as optional instead of relying on an empty array. |
inlineProducts | object[] | Products shown inline, with their prices and offers. When `include.shopping` is `false` or not set, `result` does not contain this field at all, so treat it as optional instead of relying on an empty array. |
entities | object[] | Entities parsed from the response (if any). With include.shopping, an entity ChatGPT links to a product also carries `product`. |
map | object[] | Map entries for businesses or places parsed from the response (if any). Entries originate from the enrichment providers ChatGPT uses (Yelp, Google Business), with field names in camelCase. Only the fields filled in most often appear here; see the [API reference](/docs/reference) for every field. |
images | object[] | The pictures ChatGPT shows in its answer, beside the places and things it names, in answer order: medium image, the page each comes from (url), its title and the picture shown (thumbnail). Absent when the answer shows none. |
citationPills | object[] | Inline citation pills parsed from the response (if any), i.e. citations shown in the body of the text. Every entry represents a single (pill, source) pair; grouping on `citationPillId` rebuilds each pill. |
ads | object[] | Ads delivered with the ChatGPT response (present when include.ads is true). Several candidate ads are sent by OpenAI, yet the interface displays one at most; check `rendered` to see which ones were really displayed. |
model | string | ChatGPT model that produced the answer. Empty when the response came through OpenAI's mobile-web format. |
The app answer versus OpenAI’s developer API
This endpoint returns the answer a ChatGPT user sees. OpenAI’s developer API returns a completion you generate yourself, and the two answer different questions.
The developer API has a web search tool. Its guide (checked 2026-09-17, developers.openai.com) says the response carries url_citation annotations with the URL, title and location of each cited source, a separate sources list of every URL retrieved, the search actions the model took, and an optional user location (country, city, region, timezone). The guide does not describe shopping cards or ads. You also choose the model, system prompt and tool settings, so the output reflects your configuration.
When a brand manager asks whether ChatGPT recommends them, they mean the app. A completion with your own settings is a proxy you would have to validate against the app anyway.
The app also shows products from its shopping search, which the developer API does not describe. They are returned here as inlineProducts. For e-commerce brands those products decide visibility.
Citation fields here describe presentation. citationPills records which sources were grouped into each inline chip. sources[].footnote would mark a source listed but cited by no chip; it is false on every answer served today, since every listed source is cited by a chip. That is closer to what a reader clicks than a list of retrieved URLs. Provider model APIs vs the answers users see compares the two side by side.
Cost, free requests and usage limits
At the Starter tier a ChatGPT request costs $0.0006. Every include flag is free. Failed requests cost nothing. Higher tiers, reached by 30-day spend, are cheaper: a request is $0.42–$0.60 per 1,000 across tiers. Every new account gets 100 free requests, no card.
| Limit | Value | When you reach it |
|---|---|---|
| Requests per second | No limit | Never |
| Queued async tasks | About 100,000 tasks of a typical prompt, more of shorter ones | The task or batch is rejected whole with 429 QUEUE_LIMIT_EXCEEDED |
| Balance | Prepaid, topped up from $1 | Requests are refused with 403 INSUFFICIENT_CREDITS until you top up |
There is no concurrency cap. The limits are in rate limits; prices per tier are on pricing.
An annotated response
The excerpt follows the result schema for POST /v1/monitor/chatgpt. Values are illustrative; the request set include.markdown, include.searchQueries and include.shopping to true.
{
"success": true,
"result": {
// Plain answer text; markdown is present because include.markdown was true.
"text": "For flat feet, look for stability shoes with a firm midsole...",
"markdown": "For **flat feet**, look for stability shoes with a firm midsole...",
// Every cited page, in order. datePublished is omitted when unknown, never null.
"sources": [
{
"position": 1,
"url": "https://www.example-running.com/guides/flat-feet",
"label": "Best Running Shoes for Flat Feet",
"description": "We tested stability models on...",
"footnote": false,
"datePublished": "2026-08-02"
}
],
// One entry per (chip, source) pair. ChatGPT's citationPillId starts at 0.
// position points into result.sources (1-based).
"citationPills": [
{
"url": "https://www.example-running.com/guides/flat-feet",
"label": "Best Running Shoes for Flat Feet",
"domain": "example-running.com",
"citationPillId": 0,
"type": "searchResult",
"position": 1
}
],
// Products from ChatGPT's shopping search, each once. Present only when include.shopping is true.
"inlineProducts": [
{
"render_as": "inline",
"product": {
"title": "Example Stability Runner 5",
"position": 1,
"id": "1234567890",
"url": "https://www.example-store.com/p/stability-runner-5",
"price": "$139.95",
"image_urls": ["https://images.example-store.com/runner-5.jpg"],
"offers": [
{
"merchant_name": "Example Store",
"product_name": "Example Stability Runner 5",
"url": "https://www.example-store.com/p/stability-runner-5",
"price": "$139.95",
"available": true
}
]
}
}
],
// Every brand and product the answer names: ChatGPT's full name for it and its category.
"entities": [{ "type": "product", "name": "Example Stability Runner 5" }],
// Nullable.
"model": null
}
}
Handle these in your client:
shoppingCardsandinlineProductsare absent wheninclude.shoppingis off. A requested list (searchQueries,ads,shoppingCards,inlineProducts) is an empty array when it has nothing.searchQueries,adsandshoppingCardsare empty on the ChatGPT answers served today.inlineProductsholds the products from ChatGPT’s shopping search.entitieslists every brand and product the answer names, those products too. Itsnameis ChatGPT’s full name for it, which can differ from the words intext; itstypeis its category (product,software, …). Withinclude.shopping, an entity ChatGPT links to a product also carriesproduct: its price, URL and merchant offers, in the shape of an inline product.entitiesandcitationPillsare absent when there are none.mapholds the local businesses the answer names as cards (restaurants, shops), each once in answer order:name,address,latitude,longitude,categories,rating,reviewCount,description,phone,websiteUrl, ChatGPT’s ownidandproviderfor it, andposition. The same businesses are inentitieswithtypelocal_business.imagesholds the pictures the answer shows beside what it names:thumbnail(the picture),url(the page it comes from),titleand sizes.mapandimagesare absent when the answer has none.modelis nullable.- No merchant field is filled on a product: read
offers[0].merchant_name. product.positionininlineProductsis its own 1-based sequence. InshoppingCardsit is a flat rank across all cards, not reset per card.
Synchronous calls
A synchronous call waits for the answer (up to five minutes). Use it for interactive tools and spot checks.
Python (requests)
import os
import requests
API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
resp = requests.post(
f"{API}/v1/monitor/chatgpt",
headers=HEADERS,
json={
"prompt": "What are the best running shoes for flat feet?",
"country": "US",
"include": {"markdown": True, "searchQueries": True},
},
timeout=330,
)
if resp.status_code != 200:
raise RuntimeError(f"{resp.status_code} {resp.headers.get('X-Request-Id')} {resp.text}")
result = resp.json()["result"]
cited = [s["url"] for s in result.get("sources", [])]
print(result["text"][:200])
print("searched:", result.get("searchQueries", []))
print("cited:", cited)
print("credits charged:", resp.headers.get("X-Credits-Charged"))
TypeScript (fetch)
const API = "https://api.answerline.dev";
const res = await fetch(`${API}/v1/monitor/chatgpt`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
prompt: "What are the best running shoes for flat feet?",
country: "US",
include: { markdown: true, shopping: true },
}),
signal: AbortSignal.timeout(330_000),
});
if (!res.ok) throw new Error(`${res.status} ${res.headers.get("X-Request-Id")} ${await res.text()}`);
const { result } = await res.json();
const products = (result.inlineProducts ?? []).map((item: any) => item.product);
console.log(result.text.slice(0, 200));
console.log(products.map((p: any) => `${p.position}. ${p.title} ${p.price} (${p.offers?.[0]?.merchant_name})`));
console.log("credits charged:", res.headers.get("X-Credits-Charged"));
Batches: async tasks and a webhook
For scheduled prompt sets, submit tasks. A task costs the base price, waits in your queue until it starts instead of failing, and can be delivered to a webhook. POST /v1/async/task/batch takes 1 to 500 tasks; each is validated and admitted on its own and answered by index.
Derive idempotencyKey from what the task means (prompt, market, day, sample number) so a retried submission never runs a task twice.
Python: submit a batch
import datetime
import os
import requests
API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
HOOK = "https://your-app.example/hooks/answers"
day = datetime.date.today().isoformat()
prompts = ["best CRM for startups", "best CRM for real estate agents"]
tasks = [
{
"taskType": "CHATGPT",
"payload": {"prompt": prompt, "country": "US", "include": {"searchQueries": True}},
"idempotencyKey": f"crm-{i}-US-{day}",
"webhook": {"url": HOOK},
}
for i, prompt in enumerate(prompts)
]
resp = requests.post(f"{API}/v1/async/task/batch", headers=HEADERS, json=tasks, timeout=60)
resp.raise_for_status()
for item in resp.json()["results"]:
if not item["success"]:
print(tasks[item["index"]]["idempotencyKey"], item["error"]["code"])
Python: receive and verify deliveries (Flask)
The delivery body is the same JSON GET /v1/async/task/{taskId} returns: task, credits, and response, which holds result on success or error on failure. Verify the Webhook-Signature header over the raw bytes before parsing.
import hashlib
import hmac
import json
import os
import time
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["WEBHOOK_SECRET"] # whsec_... from the dashboard
def verify(raw: bytes, header: str | None, secret: str, tolerance: int = 300) -> bool:
pairs = [part.split("=", 1) for part in (header or "").split(",") if "=" in part]
stamps = [v for k, v in pairs if k == "t"]
sigs = [v for k, v in pairs if k == "v1"]
if len(stamps) != 1 or not stamps[0].isdigit() or not sigs:
return False
if abs(time.time() - int(stamps[0])) > tolerance:
return False
expected = hmac.new(secret.encode(), stamps[0].encode() + b"." + raw, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, s) for s in sigs)
@app.post("/hooks/answers")
def answers():
raw = request.get_data()
if not verify(raw, request.headers.get("Webhook-Signature"), SECRET):
abort(400)
delivery = json.loads(raw)
if delivery.get("test"):
return "", 204
task = delivery["task"]
if task["status"] == "COMPLETED":
result = delivery["response"]["result"]
# Store keyed by task["id"]: the same delivery can arrive more than once.
print(task["id"], task["idempotencyKey"], len(result.get("sources", [])), result.get("searchQueries", []))
else:
print(task["id"], "failed:", delivery["response"]["error"]["code"])
return "", 204
Deliveries can arrive out of order and more than once; deduplicate on task.id, answer 2xx within 15 seconds, and do slow work afterwards. The webhooks guide covers retries and secret rotation.
Geo targeting
| Field | Values | Notes |
|---|---|---|
country |
ISO 3166-1 alpha-2 | Required. ChatGPT answers from 213 countries. GET /v1/countries?model=chatgpt lists supported codes. |
state |
Coming soon | US-state targeting is coming soon. Until then a request that sends state is refused with a 400 and not billed. |
There is no city-level option for ChatGPT. For multi-market studies, keep the prompt text identical and vary only the market, so differences are attributable to it. The state-level answers post walks through a design.
Include flags and what they cost
| Flag | Adds to result |
Price |
|---|---|---|
include.markdown |
markdown |
0 |
include.html |
html: a link to a copy of the ChatGPT page as it was when the answer was captured (HTML snapshots) |
0 |
include.searchQueries |
searchQueries (empty on the answers served today) |
0 |
include.shopping |
inlineProducts (the shopping-search products), shoppingCards (empty on the answers served today) |
0 |
include.ads |
ads (empty on the answers served today) |
0 |
include.rawResponse |
rawResponse: the answer as normalized stream events, rebuilt from the page and its stream |
0 |
include.rawWire |
rawWire: the answer as ChatGPT streamed it, its session tokens emptied, when it was captured; off by default (what it holds) |
0 |
include.screenshot |
screenshot: a link to a full-page PNG of the ChatGPT page once the answer finished, when the page was shown (details) |
0 |
So a request with any of these flags costs the base price: $0.0006 at the Starter tier. ChatGPT decides for itself whether to search the web on every answer served today, so disableWebSearch is accepted and changes nothing (provenance.searchMode is engine-default). legacy is accepted and does not change the answer.
Provenance
Every answer’s result.provenance says how it was obtained (all fields). On ChatGPT it reads: interface the shell that answered (mobile-web is ChatGPT’s signed-out shell), loginState signed_out: a real browser on chatgpt.com, signed out, a new conversation per request, your prompt sent unchanged. model is absent when the signed-out shell names none. complete is always true: an answer ChatGPT never finished is retried, and if it never finishes the request fails, uncharged. wireCaptured says whether rawWire could be filled.
Common workloads
- AI visibility tracking: mention rate and citation share per prompt and market. See AI visibility tracking and measuring brand mentions in ChatGPT.
- Competitor analysis: which brands the answer
textnames instead of yours, which domains they are cited from, and whose products appear ininlineProducts. See competitor analysis. - Content research: the pages in
sourcesshow what ChatGPT reads on a topic, and so which pages to get your brand onto. See keyword research. - Shelf monitoring: product positions, prices and merchants in
inlineProducts. See ChatGPT shopping cards.
Pitfalls
Answers vary between runs
The same prompt, market and hour can produce different text, sources and products, so one run per prompt tells you little. Run each prompt several times per period, store every sample, and report rates (“cited in 7 of 10 runs”) with the sample size. Put the sample number in the idempotency key (...-s3-2026-09-17) so samples are distinct tasks. Answer volatility and sampling vs census cover how many samples to take.
Prompt wording is a variable
Small wording changes shift answers. Freeze the prompt set, version it, and change it deliberately; see prompt set design.
Domains and pages are different metrics
Count citation share on citationPills[].domain for domains and on sources[].url for pages. Mixing them in one metric makes the numbers meaningless.
Concurrency and retries
There is no concurrency cap and no request rate limit; see rate limits. A synchronous call is charged on success and may still be running if your connection drops, so resend it only when the request never reached the server. A 500 or 502 from a monitor endpoint is final; see retrying safely.
How failures are billed
An accepted request reserves its maximum cost (credits.creditsToCharge on a task) and is charged the actual cost on success (credits.creditsCharged, or the X-Credits-Charged header on a synchronous call). A failed request is charged nothing and its reservation is released. Tasks that never start within 4 hours fail uncharged with EXTERNAL_SERVICE_ERROR and details.cause QUEUE_WAIT_EXCEEDED; resubmit them. Full rules are in credits.
Questions
What does one ChatGPT request return and cost?
The answer text, every cited source with its position, the inline citation chips and the entities (the brands and products the answer names), without any flag. Flags add markdown, searchQueries, ads, shopping products and the raw response at no cost. On the ChatGPT answers served today, searchQueries, ads and shoppingCards come back empty; products from ChatGPT's shopping search come back in inlineProducts. At the Starter tier a request costs $0.0006.
What are the usage limits of the ChatGPT API?
There is no request rate limit and no concurrency cap. The async queue holds about 100,000 tasks of a typical prompt. Your balance is the other limit; when it runs out, requests are refused until you top up.
Is there a free tier?
Yes. Every new account gets 100 free requests, no card. They work on ChatGPT and every other live engine, a whole request each.
Can I use it for competitive analysis?
Yes. Run the prompts buyers ask about your category and compare brands across the answers: entities lists the brands and products each answer names, sources and citationPills show which domains it cites, and inlineProducts show whose products appear at what price and merchant. The ads list comes back empty on the ChatGPT answers served today.
How is this different from the OpenAI API?
This endpoint returns the answer the ChatGPT app shows its users, with its citations and shopping-search products as fields. OpenAI's developer API returns a completion from a model, system prompt and tools you configure yourself, so it does not tell you what ChatGPT users see.
Can I target a US state with ChatGPT?
Not yet. ChatGPT answers from 213 countries, and US-state targeting is coming soon. Until then a request that sends state is refused with a 400 and not billed.
Am I charged when a ChatGPT request fails?
No. A failed request is charged nothing and its reservation is released. A completed request is charged its base price, even if, for example, no products were shown.