AnswerLine Start free

Engines

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

Or queue it with POST /v1/async/task and taskType: "CHATGPT".

Countries

213 countries.

Coverage

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

* required. Nested fields are written with a dot, e.g. include.markdown.

FieldTypeDescription
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. 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 resultTypeDescription
textstringText of ChatGPT's reply
markdownstringChatGPT's reply as Markdown; returned only if include.markdown is true
sourcesobject[]Sources the response cites; an empty array if there are none.
searchQueriesstring[]The fan-out queries ChatGPT issued while producing the response (present when include.searchQueries is true) Empty on the answers served today.
shoppingCardsobject[]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. Empty on the answers served today.
inlineProductsobject[]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.
entitiesobject[]Entities parsed from the response (if any). With include.shopping, an entity ChatGPT links to a product also carries `product`.
mapobject[]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.
imagesobject[]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.
citationPillsobject[]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.
adsobject[]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. Empty on the answers served today.
modelstringChatGPT model that produced the answer. Empty when the response came through OpenAI's mobile-web format.

Full field definitions are in the API reference.

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:

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

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.

Other engines