API2026-08-124 min read

cURL AI Visibility API

You cannot curl ChatGPT or Perplexity — their UIs are JavaScript-rendered, login-walled and bot-protected, so a script gets back an empty shell. What you can curl is https://api.agentgeo.org/v1/fetches: POST a query with your ag_live_ key and the raw answer each engine gives comes back as JSON — answer text, citations, sources — ready to pipe into jq. AgentGEO is the answer-access layer for AI visibility. Your own scripts (or your users' AI agent over MCP) run the GEO/AEO analysis; the API's job is to turn an AI answer into something a shell can address — the same structured data across all six engines.

You can't curl ChatGPT — the UI is JavaScript-rendered, login-walled and bot-protected. You can curl AgentGEO: one POST returns the raw answer each of the six engines gives — ChatGPT, Perplexity, Google AI Overviews, Copilot, Gemini — with citations and sources as JSON that pipes straight into jq. An AI answer becomes something a shell script can work with.

Get your free API key → · Read the quickstart → — Free tier, no credit card required.

Start for free

Call the AI Visibility API with cURL

One POST with your ag_live_ key as a bearer token. -d carries the query and the engines you want in surfaces; the answer, citations and sources come back as JSON. Pipe to jq to pretty-print — or try the no-signup playground before you even create a key.

curl -s https://api.agentgeo.org/v1/fetches \
  -H "Authorization: Bearer ag_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"query": "best running shoes for flat feet", "surfaces": ["chatgpt"]}' \
  | jq

The full response — every field addressable by a jq path:

{
  "id": "run_84c1f2ab91d3",
  "query": "best running shoes",
  "surfaces": ["chatgpt"],
  "status": "completed",
  "recordsDelivered": 1,
  "creditsCharged": 1,
  "answers": [
    {
      "surfaceKey": "chatgpt",
      "answerText": "For most runners, top picks include ...",
      "sources": [
        { "title": "Best Running Shoes", "url": "https://example.com/guide", "position": 1 }
      ]
    }
  ]
}

The shape is identical for every engine, so one call listing several surfaces — any of chatgpt, perplexity, gemini, google_ai_overview, copilot — returns parallel citation sets, and diffing them is a jq one-liner. Who each engine cites is the core question of GEO/AEO analysis.

From there, extraction is jq. Wrap the call in a two-line shell function and the one-liners stay one-liners:

# A tiny helper so the one-liners stay one-liners
ag() {
  curl -s https://api.agentgeo.org/v1/fetches \
    -H "Authorization: Bearer ag_live_your_key_here" \
    -H "Content-Type: application/json" \
    -d "$1"
}

# Just the answer text
ag '{"query": "best running shoes for flat feet", "surfaces": ["chatgpt"]}' \
  | jq -r '.answers[0].answerText'

# Every URL the answer cited
ag '{"query": "best running shoes for flat feet", "surfaces": ["chatgpt"]}' \
  | jq -r '.answers[].sources[].url'

# Two engines, one call — per-engine citation lists, ready to diff
ag '{"query": "best running shoes for flat feet", "surfaces": ["chatgpt", "perplexity"]}' \
  | jq -r '.answers[] | .surfaceKey, (.sources[] | "  " + .url)'

And because it's just curl, it schedules. Append each run as a timestamped JSON line and you have a minimal visibility monitor that runs unchanged in cron or CI:

# Append one timestamped JSON line per run — cron- and CI-friendly
curl -s https://api.agentgeo.org/v1/fetches \
  -H "Authorization: Bearer ag_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"query": "best running shoes for flat feet", "surfaces": ["chatgpt", "perplexity"]}' \
  | jq -c '. + {fetchedAt: (now | todate)}' >> visibility.jsonl

# crontab -e — track the answer every morning at 07:00
# 0 7 * * * /usr/local/bin/track-visibility.sh

What the cURL API gives you

AgentGEO is a thin answer-access layer, not a dashboard you log into. The API hands your scripts the raw material; whatever you pipe, diff, alert on or archive is yours.

  • Six engines, one contract — ChatGPT, Perplexity, Google AI Overviews, Google AI Mode, Copilot and Gemini answer through the same endpoint; switching engines is editing one JSON array in your -d payload.
  • Citations at a stable `jq` path — every cited source lives at .answers[].sources[] with title, url and position, so extraction is a one-liner, not a parser.
  • Managed-scraper engine, maintained for you — a plain HTTPS POST that behaves like every other API you script: no browser, no cookies, no bot wall between you and the data.
  • Provider metadatasurfaceKey and providerFields on every response keep a long-running JSONL log auditable as the models underneath change.
  • MCP connection, too — the same data over MCP via agentgeo-mcp on npm, for the day the consumer is Claude Code or Cursor instead of a script.
  • Usage-based billing with a spend cap — free tier without a credit card, then subscription plus overage; a forgotten cron loop can't surprise you. Never per-seat.

Common problems with DIY answer-scraping from the shell

The engines' UIs are apps, not documents — which is why pure shell against them goes nowhere, and why every DIY route ends with a headless browser: the opposite of what you reached for curl to get.

  • curl gets an empty shell of the page. ChatGPT and Perplexity render answers client-side; fetch the page from a script and the body is a JavaScript bootstrap with no answer text anywhere in it.
  • No session from a script. The UIs sit behind login and rotating session tokens — nothing a shell script can legitimately hold on to, so requests never reach an answer at all.
  • Bot walls flag curl instantly. Cloudflare-class protection exists precisely to stop non-browser clients, and curl's TLS and header fingerprint is the first thing it filters.
  • Parsing rendered HTML in shell is hopeless. grep and sed against a hydrated React DOM is write-only code — and it dies on the next frontend deploy anyway.
  • The workaround erases the point. Each dead end pushes you toward bolting on headless Chrome — and once a browser farm sits behind your 'one-liner', the simplicity that made curl attractive is gone.
curl against the AI UIscurl against AgentGEO
What comes backAn empty JS bootstrap shellThe full answer as JSON
AuthSession cookies you can't scriptOne Authorization: Bearer header
Parsinggrep/sed against a hydrated DOMjq one-liners
Bot wallsFingerprinted and blockedManaged-scraper engine, maintained for you
AutomationBolt on headless Chrome, lose the one-linercron + curl + jq, done

AgentGEO is the answer-access layer; a shell script appending JSONL is already a minimal monitoring product. The data lands on your disk, in your format, under your control — not inside someone else's dashboard.

MCP: when the consumer is an agent, not a script

If the thing consuming answers is an AI agent instead of a shell script — Claude Code, Cursor, Codex or any MCP client — the same data is available over MCP via the agentgeo-mcp npm package. The install is, fittingly, one command:

claude mcp add agentgeo -- npx -y agentgeo-mcp --key ag_live_your_key_here

Who builds on this

Anyone who wants answers without writing an application: a quick audit before a client call, a CI job that fails when your brand drops out of an answer, a cron loop building citation history in flat files. It's also the fastest way to evaluate the API before committing real code — start in the no-signup playground, graduate to a key. And if you were about to script against ChatGPT's UI instead, read that comparison first.

Frequently asked questions

Can I call the AI visibility API from cron or CI?
That's the sweet spot. The JSONL pattern above — curl, jq -c, append to a file — runs unchanged in crontab, GitHub Actions or any CI step, with nothing to install beyond curl and jq. The free tier and the spend cap on usage-based billing make an always-on scheduled check safe to leave running.
Do I need jq?
No — the endpoint returns plain JSON you can pipe into anything, or nothing. jq just makes shell extraction one-liners: .answers[0].answerText for the text, .answers[].sources[].url for the citations. Every example on this page works without it if you'd rather post-process elsewhere.
How do I query multiple engines in one call?
List them in the surfaces array: "surfaces": ["chatgpt", "perplexity", "gemini", "google_ai_overview", "copilot"]. You get one answers[] entry per engine in a single response — ready to print per-engine citation lists with one jq expression — and the response reports recordsDelivered and creditsCharged so cost per run stays visible.
Where do I get an API key?
Sign up at /onboarding — the free tier requires no credit card. Keys look like ag_live_... and go in the Authorization: Bearer header. Want to see a live response before signing up at all? The raw-answer playground at /tools/raw-answer-fetch runs a fetch with no key.
Why can't I just curl ChatGPT or Perplexity directly?
Their UIs are JavaScript-rendered single-page apps behind login and bot protection — curl gets back a script-bootstrap shell with no answer in it, and anything smarter means running a headless browser, which defeats the point of a shell one-liner. This API exists to make an AI answer addressable by a plain HTTP request: the same data, but as JSON with citations.

Keep reading