API2026-08-104 min read

TypeScript AI Visibility API

Query the raw answers ChatGPT, Perplexity, Google AI Overviews, Google AI Mode, Copilot and Gemini actually give — from TypeScript, against a contract small enough to write down: one union type and three interfaces. POST a query to https://api.agentgeo.org/v1/fetches with your ag_live_ key and the answer text, citations and sources come back as JSON the compiler can hold you to. AgentGEO is the answer-access layer for AI visibility. Your own code (or your users' AI agent over MCP) runs the GEO/AEO analysis — the API hands you raw data whose shape is identical across all six engines, which is exactly what makes it typeable in the first place.

Query the raw answers ChatGPT, Perplexity, Google AI Overviews, Google AI Mode, Copilot and Gemini actually return — from TypeScript, against a response small enough to describe with three interfaces. Scraped HTML is any: its shape changes whenever the UI does. A stable JSON API is an interface you can write down — answer text, citations and sources, checked by the compiler.

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

Start for free

Call the AI Visibility API from TypeScript

The request is one fetch; the value is the contract. Below: a six-engine SurfaceKey union, the three interfaces that describe the entire response, and a typed fetchAnswers wrapper. That wrapper is the whole SDK — and you own it.

type SurfaceKey =
  | "chatgpt"
  | "perplexity"
  | "gemini"
  | "google_ai_overview"
  | "copilot";

interface Source {
  title: string;
  url: string;
  position: number;
}

interface Answer {
  surfaceKey: SurfaceKey;
  answerText: string;
  sources: Source[];
}

interface FetchRun {
  id: string;
  query: string;
  surfaces: SurfaceKey[];
  status: string;
  recordsDelivered: number;
  creditsCharged: number;
  answers: Answer[];
}

async function fetchAnswers(
  query: string,
  surfaces: SurfaceKey[],
): Promise<FetchRun> {
  const resp = await fetch("https://api.agentgeo.org/v1/fetches", {
    method: "POST",
    headers: {
      "Authorization": "Bearer ag_live_your_key_here",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ query, surfaces }),
  });
  if (!resp.ok) throw new Error(`AgentGEO ${resp.status}: ${await resp.text()}`);
  return (await resp.json()) as FetchRun;
}

const run = await fetchAnswers("best running shoes for flat feet", [
  "chatgpt",
  "perplexity",
]);

for (const answer of run.answers) {
  console.log(answer.surfaceKey, "→", answer.answerText);
}

The JSON you're typing against — this is the entire surface those interfaces need to cover:

{
  "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 }
      ]
    }
  ]
}

One SurfaceKey union covers all six engines because the response shape never varies between them — run the same query across chatgpt, perplexity, gemini, google_ai_overview and copilot and diff the citation sets. That diff is the heart of any GEO/AEO analysis, and here it stays typed end to end.

Because sources is typed, helpers built on top stay typed too. Extracting every cited domain — the input to any share-of-voice metric — is a few honest lines:

// Every domain an engine cited — hostname-level share of voice
function citedDomains(run: FetchRun): Set<string> {
  const domains = new Set<string>();
  for (const answer of run.answers) {
    for (const src of answer.sources) {
      domains.add(new URL(src.url).hostname);
    }
  }
  return domains;
}

const domains = citedDomains(run);
console.log([...domains]);                 // e.g. ["runnersworld.com", ...]
console.log(domains.has("yourbrand.com")); // the GEO question, in one line

What the TypeScript API gives you

AgentGEO stays a thin answer-access layer, not a closed dashboard. The API delivers raw, structured data; your TypeScript codebase (or your users' AI agent) turns it into product.

  • Six engines, one union type — ChatGPT, Perplexity, Google AI Overviews, Google AI Mode, Copilot and Gemini share a single request/response contract, so SurfaceKey is the only place engines are enumerated.
  • A shape you can write an interface forFetchRun, Answer, Source: three interfaces cover the whole response, and the compiler enforces them everywhere the data flows.
  • Citations as typed dataAnswer.sources is Source[] with title, url and position. Autocomplete works, strict null checks work, and nothing degrades to any.
  • Managed-scraper engine, maintained for you — no Playwright underneath the types, no proxies, no CAPTCHA walls. The runtime path is as clean as the type layer.
  • MCP connection, too — the same data is exposed over MCP via agentgeo-mcp on npm, so Claude Code, Cursor, Codex or any MCP client can consume the identical contract.
  • Usage-based billing with a spend cap — free tier with no credit card, then subscription plus overage. Priced by usage, never per seat.

Common problems with DIY answer-scraping in TypeScript

In TypeScript the scraping route carries a special indignity: you chose the language for the guarantees, and scraped HTML is where guarantees go to die. Drive a headless browser into ChatGPT or Perplexity and everything the compiler can vouch for ends at the DOM boundary.

  • Typing scraped DOM is fiction. The true type of ChatGPT's rendered markup is 'whatever last week's deploy produced' — so scraper code degrades to any, unknown and casts, and strict mode stops meaning anything.
  • It compiles green and breaks at runtime. document.querySelector(".citation") type-checks forever; the class it targets was renamed last Tuesday. The compiler cannot see the DOM you will actually receive.
  • Layout changes bypass the type system entirely. The breaking change lands in their frontend deploy, not your commit — no red squiggle, no failing build, just empty sources arrays quietly landing in production.
  • Hand-maintained interfaces for six engines' HTML. Six undocumented markup structures, all shifting independently. Those interfaces are stale a week after you write them — false confidence is worse than no types.
  • Under the types it's still a scraper. Playwright, stealth patches, proxies, CAPTCHA walls: TypeScript hardens your code, not the hostile, unversioned input that code is parsing.
DIY scraping in TypeScriptAgentGEO API
Response typeany — whatever the DOM is todayFetchRun — three interfaces you own
BreakageCompiles green, fails at runtimeContract change = a visible diff in review
CitationsUntyped DOM walking per engineSource[] with title, url, position
Engine coverageSix HTML structures, six fake typesOne SurfaceKey union
InfrastructurePlaywright, proxies, CAPTCHA wallsManaged-scraper engine, maintained for you

The interfaces above are the entire integration surface. AgentGEO stays the answer-access layer underneath; the typed domain model, the dashboard and the alerts you build on it are your product — and you own the data feeding them.

The MCP route: same contract, inside your agent

The same data is exposed over MCP. When the consumer is an agent rather than your own code — Claude Code, Cursor, Codex or any MCP client — one command wires it up via the agentgeo-mcp npm package, and the agent pulls answers and runs the GEO analysis itself:

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

Who builds on this

Teams building the dashboard itself: rank-trackers for the AI-answer era, agency reporting products, internal share-of-voice tools — anywhere AI-visibility data becomes a typed domain model inside a larger codebase. If you compared closed platforms like Profound and wanted raw, typed data instead of a rented UI — or you were weighing scraping ChatGPT directly — this is the layer underneath.

Ready to pull your first typed answer? Start free → · Follow the 5-minute quickstart →

Start for free

Frequently asked questions

Are official TypeScript types published for the AI visibility API?
The response shape is deliberately small enough that the types fit on one screen: a SurfaceKey union and three interfaces — Source, Answer and FetchRun — exactly as written above. You own them in your codebase, so there is no SDK version to chase and no codegen step; if the contract ever grows, updating them is a one-line diff you get to review.
Should I validate the response with Zod?
Optionally. TypeScript types are compile-time only, so if you want runtime guarantees at the trust boundary, mirror FetchRun in a Zod (or Valibot) schema and .parse() the JSON before the cast. Many teams skip it because the endpoint is versioned (/v1/) and the shape is identical across all six engines — a cast plus a resp.ok check covers the practical failure modes.
Does it work with Next.js and edge runtimes?
Yes. The wrapper above is plain fetch, so it runs in Next.js route handlers, server actions and middleware, on the Vercel Edge Runtime, and in Cloudflare Workers, Deno and Bun. There is no Node-only dependency — and no headless browser to exile into serverExternalPackages.
How is this different from the Node.js page?
Same endpoint, same key, same JSON. The Node.js page at /geo-api/node covers plain-JavaScript usage and parallel fan-out across engines; this page adds the typed contract — the SurfaceKey union and the response interfaces. That contract is the thing scraping can never give you, because you cannot write a stable interface for HTML that changes under your feet.
What engines are in the SurfaceKey union?
Six: chatgpt, perplexity, gemini, google_ai_overview, google_ai_mode and copilot — that is ChatGPT, Perplexity, Gemini, Google AI Overviews and Copilot. Querying another engine means adding one literal to an array, and the compiler autocompletes valid keys and rejects typos at build time instead of in production.

Keep reading