Go AI Visibility API
Query the raw answers ChatGPT, Perplexity, Google AI Overviews, Google AI Mode, Copilot and Gemini actually give — from Go, with net/http and three structs you own. 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 clean JSON that encoding/json decodes in one call. No chromedp, no DevTools protocol, no proxy pool. AgentGEO is the answer-access layer for AI visibility — a golang GEO API in the plainest sense. Your own code (or your users' AI agent over MCP) runs the GEO/AEO analysis; the API just hands you the raw data, structured and identical across all six engines.
Query the raw answers ChatGPT, Perplexity, Google AI Overviews, Google AI Mode, Copilot and Gemini actually return — from Go, with nothing beyond net/http and encoding/json. No chromedp, no rod, no headless Chrome to orchestrate: one POST, and the answer text, citations and sources decode into typed structs your goroutines can fan out over.
Get your free API key → · Read the quickstart → — Free tier, no credit card required.
Start for freeCall the AI Visibility API from Go
POST to https://api.agentgeo.org/v1/fetches with your ag_live_ key as a bearer token and the engines you want in the surfaces array. The whole integration is standard library — net/http for the call, encoding/json for decoding into three structs you define once and own. (Want to see the payload before writing code? The raw answer playground runs a query in the browser, no signup.)
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
type Source struct {
Title string `json:"title"`
URL string `json:"url"`
Position int `json:"position"`
}
type Answer struct {
SurfaceKey string `json:"surfaceKey"`
AnswerText string `json:"answerText"`
Sources []Source `json:"sources"`
}
type FetchRun struct {
ID string `json:"id"`
Query string `json:"query"`
Surfaces []string `json:"surfaces"`
Status string `json:"status"`
RecordsDelivered int `json:"recordsDelivered"`
CreditsCharged int `json:"creditsCharged"`
Answers []Answer `json:"answers"`
}
// The API waits up to 180s on slow surfaces — keep the client above that.
var client = &http.Client{Timeout: 200 * time.Second}
func fetchRun(query string, surfaces ...string) (*FetchRun, error) {
body, err := json.Marshal(map[string]any{"query": query, "surfaces": surfaces})
if err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodPost, "https://api.agentgeo.org/v1/fetches", bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer ag_live_your_key_here")
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("agentgeo: unexpected status %s", resp.Status)
}
var run FetchRun
if err := json.NewDecoder(resp.Body).Decode(&run); err != nil {
return nil, err
}
return &run, nil
}
func main() {
// surfaces: chatgpt | perplexity | gemini | google_ai_overview | google_ai_mode | copilot
run, err := fetchRun("best running shoes for flat feet", "chatgpt")
if err != nil {
log.Fatal(err)
}
for _, answer := range run.Answers {
fmt.Println(answer.SurfaceKey, "→", answer.AnswerText)
for _, src := range answer.Sources { // cited sources, in order
fmt.Println(" ", src.Position, src.Title, src.URL)
}
}
}Everything lands in one FetchRun value — plain JSON on the wire, no scraping artifacts to scrub:
{
"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 response shape is identical across all six engines, so the same three structs cover chatgpt, perplexity, gemini, google_ai_overview, google_ai_mode and copilot. Fan one query across all six and diff the citation sets — that diff is the core of any GEO/AEO analysis you'd want to build.
Fanning a query across every engine is where Go shines — one goroutine per surface, a sync.WaitGroup, then print which URLs each engine cited:
// Fan one query across all six engines — reuses fetchRun and the structs above.
// Add "sync" to the import block.
func main() {
surfaces := []string{"chatgpt", "perplexity", "gemini", "google_ai_overview", "google_ai_mode", "copilot"}
var wg sync.WaitGroup
runs := make([]*FetchRun, len(surfaces))
for i, surface := range surfaces {
wg.Add(1)
go func(i int, surface string) {
defer wg.Done()
run, err := fetchRun("best running shoes for flat feet", surface)
if err != nil {
log.Printf("%s: %v", surface, err)
return
}
runs[i] = run // each goroutine owns its slot — no mutex needed
}(i, surface)
}
wg.Wait()
for _, run := range runs {
if run == nil {
continue
}
for _, answer := range run.Answers {
fmt.Println(answer.SurfaceKey, "cites:")
for _, src := range answer.Sources {
fmt.Println(" ", src.URL)
}
}
}
}What the Go API gives you
AgentGEO is a thin answer-access layer, not another closed dashboard. The API delivers the raw material; your Go service, worker or cron binary (or your users' AI agent) does the analysis.
- Six engines, one contract — ChatGPT, Perplexity, Google AI Overviews, Google AI Mode, Copilot and Gemini behind one request/response shape, so covering another engine is one more string in the
surfacesslice. - Citations and sources, structured — every answer decodes straight into
[]Sourcewith title, URL and position, ready to store, rank or diff. No goquery, no regexing links out of markup. - Managed-scraper engine, maintained for you — answers come through supported access paths, so there is no chromedp session to supervise, no proxy list to rotate and no IP ban to recover from mid-run.
- Provider metadata — every response carries
surfaceKeyandproviderFields, so the dataset you build stays auditable as the engines underneath evolve. - MCP connection, too — the same data is exposed over MCP through the
agentgeo-mcppackage, so Claude Code, Cursor, Codex or any MCP client can pull answers and run the GEO analysis itself, no glue code. - Usage-based billing with a spend cap — start on the free tier with no credit card, then subscription plus overage, priced by usage and never per seat.
Common problems with DIY answer-scraping in Go
The Go-native instinct is chromedp or rod: drive ChatGPT or Perplexity over the DevTools protocol and pull answers out of the DOM. The first version compiles quickly — Go always does — and then the maintenance begins.
- chromedp and rod become the project. Driving headless Chrome over the DevTools protocol means allocator contexts, cancel funcs and lifecycle hooks for every tracked query — a browser-orchestration layer bolted onto what should be one HTTP call.
- DevTools-protocol flakiness. Targets detach, websockets drop and
context deadline exceededfires nondeterministically under load. Retrying a stateful browser session is far harder than retrying a stateless POST. - No stable selectors on chat UIs. ChatGPT and Perplexity render through client-side JS with hashed, ever-shifting class names; your node queries start returning empty strings after a frontend deploy you'll never see coming.
- Goroutine leaks around browser contexts. Fanning out means goroutines holding Chrome tabs; one missed
cancel()and memory climbs until the pod is OOM-killed. Add residential proxies and backoff to dodge the anti-bot walls, and none of it ships product. - CI images that must bundle Chromium. The day your binary needs a browser, cross-compiling to a scratch container is over — you now ship 300 MB images with a pinned Chrome, fonts and shared libraries, for a marketing metric.
| DIY scraping in Go | AgentGEO API | |
|---|---|---|
| Setup | chromedp/rod, Chrome binaries, proxy rotation | net/http + a key — standard library only |
| Parsing | DOM queries against hashed class names | Stable JSON, identical across engines |
| Citations | Hand-rolled extraction per engine | Structured answers[].sources[] |
| Builds & CI | 300 MB images bundling Chromium | A plain HTTPS call — scratch containers fine |
| Adding an engine | A new scraper and browser pool | One more key in the surfaces array |
AgentGEO is the answer-access layer; the ranking, alerting or dashboards you build in Go on top of it are yours. You own the raw data and ship it inside your own product instead of renting a login to someone else's.
Prefer MCP? Same data, no glue code
Everything the REST endpoint returns is also exposed over MCP. When the consumer is an AI agent rather than a Go service — Claude Code, Cursor, Codex or any MCP client — one command wires it up, 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
Mostly backend engineers embedding AI-visibility tracking in an existing product — a Go worker that fans a keyword set across six engines and writes citations to Postgres is an afternoon of code — plus agencies white-labeling GEO/AEO reporting for clients. If you compared closed dashboards like Profound and wanted the raw data underneath, or you were sketching your own scraper, this is the layer to build on.
Ready to pull your first answer? Start free → · Follow the 5-minute quickstart →
Start for freeFrequently asked questions
- Do I need chromedp or a headless browser to track AI answers in Go?
- No. AgentGEO's managed scrapers run server-side, so the whole integration is a
net/httpPOST tohttps://api.agentgeo.org/v1/fetcheswith aBearer ag_live_key. There is no Chrome to launch, no DevTools websocket to keep alive and no proxy pool to rotate — the failure modes of browser automation never enter your codebase. - How do I model the API response in Go?
- Three structs you own:
FetchRun(id, query, surfaces, status, credits, answers),Answer(surfaceKey,answerText,sources) andSource(title,url,position).encoding/jsondecodes the response into them directly, and because the shape is identical across engines, the same three structs cover all six. - Does it work in containers, serverless and CI?
- Yes. It is a plain HTTPS call, so it runs in scratch containers, AWS Lambda, Cloud Run and any CI job with outbound network — no bundled Chromium, no glibc gymnastics, no 300 MB images. Cross-compilation stays trivial because there is nothing to link against.
- What's the right concurrency pattern for fanning out queries?
- Either put all six engines in a single request —
"surfaces": ["chatgpt", "perplexity", "gemini", "google_ai_overview", "google_ai_mode", "copilot"]— and get oneanswersentry per engine, or launch one goroutine per surface with async.WaitGrouporerrgroup, bounding parallelism with a semaphore channel at higher volumes. Billing is usage-based with a spend cap, so a runaway loop can't produce a surprise invoice. - Which AI engines does the Go API cover?
- Six: ChatGPT, Perplexity, Google AI Overviews, Google AI Mode, Copilot and Gemini. The request and response shape is identical across all of them, so adding an engine to your tracker is one more string in the
surfacesslice — no new scraper, no new parser.
Keep reading