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 freeCall 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 lineWhat 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
SurfaceKeyis the only place engines are enumerated. - A shape you can write an interface for —
FetchRun,Answer,Source: three interfaces cover the whole response, and the compiler enforces them everywhere the data flows. - Citations as typed data —
Answer.sourcesisSource[]withtitle,urlandposition. Autocomplete works, strict null checks work, and nothing degrades toany. - 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-mcpon 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,unknownand casts, andstrictmode 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
sourcesarrays 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 TypeScript | AgentGEO API | |
|---|---|---|
| Response type | any — whatever the DOM is today | FetchRun — three interfaces you own |
| Breakage | Compiles green, fails at runtime | Contract change = a visible diff in review |
| Citations | Untyped DOM walking per engine | Source[] with title, url, position |
| Engine coverage | Six HTML structures, six fake types | One SurfaceKey union |
| Infrastructure | Playwright, proxies, CAPTCHA walls | Managed-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_hereWho 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 freeFrequently 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
SurfaceKeyunion and three interfaces —Source,AnswerandFetchRun— 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
FetchRunin 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 aresp.okcheck 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 intoserverExternalPackages. - 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
SurfaceKeyunion 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_modeandcopilot— 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