AnswerLine Start free

Engines

Grok API

Grok's answer with its cited sources, including X posts, and the searches it ran.

Price

$2.18 per 1,000 requests at Starter, down to $2.18 at Max, deducted from your prepaid USD balance.

Endpoint

POST /v1/monitor/grok

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

Countries

Any country.

Coverage

Request

curl -X POST "https://api.answerline.dev/v1/monitor/grok" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"Which electric car brands do people recommend?","country":"US"}'

Request options

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

FieldTypeDescription
prompt * string Prompt submitted to Grok, 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.markdown boolean Add a markdown version of the response to the result
include.html boolean A link to the answer's own public page on grok.com, in `html`, to check the answer against. No extra cost.
include.rawResponse boolean Return the answer as normalized stream events. For the engine's answer as received, use `include.rawWire`.
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
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 the answer from Grok
markdownstringGrok's answer rendered as Markdown (returned if include.markdown is set to true)
sourcesobject[]Sources the response cites, with extra metadata
searchQueriesstring[]Searches Grok ran to collect material for the response
relatedQueriesstring[]Follow-up questions Grok suggests under its answer, in its order. Absent when it suggests none.
modelstringIdentifier of the model that produced the response

Full field definitions are in the API reference.

The Grok app answer versus xAI’s API

This endpoint returns the answer a user gets from the consumer Grok app, with the sources it cited (web pages and X posts), the searches it ran and the model identifier.

xAI’s developer documentation (checked 2026-09-17, docs.x.ai) describes search tools for API calls: a web search tool that lets Grok search the web in real time and browse pages, an X search tool, and domain filters (allowed_domains, excluded_domains). With the API you choose the model, the instructions and which tools and domains are allowed.

Those settings shape what your call cites, so an API run tells you what Grok cites under your configuration. The app answer shows what it cites for the public.

Grok also cites X posts alongside web pages. Brands whose reputation lives in posts and threads need to know which posts and authors the public answer draws on, and sources[].creator makes that attributable. Run the same prompt set through ChatGPT or Gemini and you get each consumer product’s answer in one schema family, so share-of-voice numbers compare directly.

An annotated response

The excerpt follows the result schema for POST /v1/monitor/grok. Values are illustrative; the request set include.markdown to true.

{
  "success": true,
  "result": {
    "text": "Recommendations vary by budget. Owners frequently mention...",
    "markdown": "Recommendations vary by budget.\n\n- **Range:** owners frequently mention...",
    // Web pages first, then X posts.
    "sources": [
      {
        "position": 1,
        "url": "https://www.example-ev-review.com/long-range-evs-2026",
        "label": "Longest-range EVs of 2026",
        "description": "Range test results for...",
        "preview": "We drove twelve EVs until empty..."
      },
      {
        "position": 2,
        "url": "https://x.com/example_owner/status/1900000000000000000",
        "label": "Example Owner",          // the author's display name
        "preview": "Six months in, charging on the road has been...",  // the post text
        "creator": "example_owner"        // the handle
      }
    ],
    // The searches Grok ran on the web and on X. Absent when there are none.
    "searchQueries": ["electric car owner recommendations 2026"],
    // Nullable model identifier.
    "model": "grok-example"
  }
}

Parsing notes:

Synchronous calls

A synchronous call waits up to five minutes.

Python (requests)

import os
from urllib.parse import urlparse
import requests

API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
X_HOSTS = {"x.com", "www.x.com", "twitter.com", "www.twitter.com", "mobile.twitter.com"}

resp = requests.post(
    f"{API}/v1/monitor/grok",
    headers=HEADERS,
    json={"prompt": "Which electric car brands do people recommend?", "country": "US"},
    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"]
sources = result.get("sources", [])
posts = [s for s in sources if urlparse(s.get("url", "")).netloc.lower() in X_HOSTS]
print("model:", result.get("model"))
print("searched:", result.get("searchQueries", []))
print(f"{len(posts)} of {len(sources)} sources are X posts")
for s in posts:
    print(" ", s.get("creator"), s["url"])

TypeScript (fetch)

const API = "https://api.answerline.dev";
const X_HOSTS = new Set(["x.com", "www.x.com", "twitter.com", "www.twitter.com", "mobile.twitter.com"]);

const res = await fetch(`${API}/v1/monitor/grok`, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ prompt: "Is Example Motors reliable?", country: "US", include: { markdown: 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 bySite = new Map<string, number>();
for (const s of result.sources ?? []) {
  const host = new URL(s.url).hostname.toLowerCase();
  const key = X_HOSTS.has(host) ? "X" : host;
  bySite.set(key, (bySite.get(key) ?? 0) + 1);
}
console.log(result.model, [...bySite.entries()]);
console.log("charged (USD):", res.headers.get("X-Credits-Charged"));

Batches: async tasks and a webhook

Tasks cost $0.00218, queue, and post to your webhook when they finish. POST /v1/async/task/batch takes 1 to 500 tasks, admitted independently and answered by index. Reputation monitoring may run the same questions hourly; an idempotency key that includes the hour stops a retried scheduler from submitting twice.

Python: an hourly reputation batch

import datetime
import os
import requests

API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
hour = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H")

questions = [
    "Is Example Motors reliable?",
    "What are people saying about the Example Motors recall?",
    "Example Motors vs Sample Cars: which is better?",
]
tasks = [
    {
        "taskType": "GROK",
        "payload": {"prompt": q, "country": "US"},
        "idempotencyKey": f"rep-{i}-US-{hour}",
        "priority": 8,
        "webhook": {"url": "https://your-app.example/hooks/answers"},
    }
    for i, q in enumerate(questions)
]

resp = requests.post(f"{API}/v1/async/task/batch", headers=HEADERS, json=tasks, timeout=60)
resp.raise_for_status()
body = resp.json()
print(body["summary"])
for item in body["results"]:
    if not item["success"] and item["error"]["code"] != "RESOURCE_ALREADY_EXISTS":
        print(tasks[item["index"]]["idempotencyKey"], item["error"]["code"])

RESOURCE_ALREADY_EXISTS means a task with that key was created earlier, which is the expected result when a scheduler retries.

Python: receive and verify deliveries (Flask)

The body matches GET /v1/async/task/{taskId}. This receiver flags answers that mention watch terms.

import hashlib
import hmac
import json
import os
import time
from flask import Flask, abort, request

app = Flask(__name__)
WATCH = ("recall", "lawsuit", "unsafe")

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"), os.environ["WEBHOOK_SECRET"]):
        abort(400)
    delivery = json.loads(raw)
    if delivery.get("test") or delivery["task"]["status"] != "COMPLETED":
        return "", 204
    result = delivery["response"]["result"]
    text = result.get("text", "").lower()
    hits = [w for w in WATCH if w in text]
    if hits:
        print("ALERT", delivery["task"]["idempotencyKey"], hits, [s.get("url") for s in result.get("sources", [])])
    return "", 204

Deliveries can repeat and arrive in any order, so deduplicate on task.id. See webhooks for retries and signature rotation.

Geo targeting

Field Values Notes
country ISO 3166-1 alpha-2 Required. GET /v1/countries?model=grok lists supported codes.
state Not available A request that sends state is refused with a 400 and not billed.

Include flags and what they cost

Flag Adds to result Credits
include.markdown markdown 0
include.html html: a link to a copy of the answer’s public page on grok.com (HTML snapshots) Free
include.rawResponse rawResponse, the answer as normalized stream events 0
include.rawWire rawWire: always null on Grok (what it holds) 0
include.screenshot screenshot: a link to a full-page PNG of the answer’s public page on grok.com (details) 0

A Grok request is $0.00218, whatever you include.

result.provenance reads searchMode engine-default (all fields).

Common workloads

Pitfalls

When answers cite posts, the set of sources can change within hours. Compare runs from the same time window, and keep the period in the idempotency key so each window is a separate sample. Within a window, run each prompt several times and report how often a source or claim appears; see sampling vs census.

description, preview, creator and the other enrichment fields are optional. Don’t make them required columns.

Substring checks like the receiver above flag negations (“no recall”) as hits. Use them to route answers to review, not as the final classification.

High-frequency schedules can fill your queue. A submission that would overflow it is rejected whole with 429 QUEUE_LIMIT_EXCEEDED; watch GET /v1/async/status and see rate limits.

How failures are billed

An accepted request reserves its cost, $0.00218, and is charged it on success. Failed requests are charged nothing and their reservation is released, including tasks that never start within 4 hours (QUEUE_WAIT_EXCEEDED). Synchronous calls are charged on success even if your connection dropped, so resend them only when the request never reached the server. See credits.

Questions

How much does a Grok request cost?

A request costs $0.00218. No include flag changes that: markdown, rawResponse and rawWire are free. include.html links a copy of the answer's public page on grok.com and include.screenshot a picture of it, also free.

Does the Grok endpoint return X posts as sources?

Yes. X posts come back in result.sources beside web pages. A post carries url (https://x.com/{handle}/status/{id}), label (the author's display name), preview (the post text) and creator (the handle). A web page carries url, label, description and preview.

How is this different from xAI's API?

xAI's API gives developers search tools for their own Grok calls. This endpoint returns the answer the consumer Grok app shows, with its cited sources, the searches it ran and the model identifier as fields.

Does Grok return citation pills like the other engines?

No. The Grok result has text, markdown, sources, searchQueries, relatedQueries, model and rawResponse. Count citations from sources.

Is a failed Grok request charged?

No. A failed request is charged nothing and its reservation is released. A completed request is charged its base price; rawResponse costs nothing extra.

Other engines