Ruby AI Visibility API
Query the raw answers ChatGPT, Perplexity, Google AI Overviews, Google AI Mode, Copilot and Gemini actually give - from Ruby, with nothing but Net::HTTP and JSON from the standard library. 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 a clean Hash. No Ferrum, no Selenium, no Capybara driving a browser outside your test suite. 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 just hands you the raw data, structured and identical across all six engines.
Read this page with an AI
Query the raw answers ChatGPT, Perplexity, Google AI Overviews, Google AI Mode, Copilot and Gemini actually return - from Ruby, with Net::HTTP and JSON, no gems. No Ferrum, no headless Chrome to drive outside your specs: one POST, and the answer text, citations and sources parse into a Hash your service objects and jobs can act on.
Get a free AI-visibility audit → · Read the quickstart → - No card, no account.
Get my free auditCall the AI Visibility API from Ruby
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, json for parsing - so it drops into a plain script, a Rake task or a Rails service object unchanged. (Want the payload first? The full response shape is in the docs.)
require "net/http"
require "json"
require "uri"
def fetch_run(query, *surfaces)
uri = URI("https://api.agentgeo.org/v1/fetches")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer ag_live_your_key_here"
req["Content-Type"] = "application/json"
req.body = JSON.generate(query: query, surfaces: surfaces)
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
raise "agentgeo: unexpected status #{res.code}" unless res.is_a?(Net::HTTPSuccess)
JSON.parse(res.body, symbolize_names: true)
end
# surfaces: chatgpt | perplexity | gemini | google_ai_overview | google_ai_mode | copilot
run = fetch_run("best running shoes for flat feet", "chatgpt")
run[:answers].each do |answer|
puts "#{answer[:surfaceKey]} → #{answer[:answerText]}"
answer[:sources].each { |src| puts " #{src[:position]} #{src[:title]} #{src[:url]}" }
endThe response is a plain JSON object - JSON.parse with symbolize_names: true gives you an idiomatic Hash, no HTML 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 Hash walk covers 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 one thread per surface - each returns the cited hosts it saw, collected into a Set:
require "set"
SURFACES = %w[chatgpt perplexity gemini google_ai_overview google_ai_mode copilot]
# One thread per engine - reuses fetch_run and returns each surface's cited hosts.
threads = SURFACES.map do |surface|
Thread.new(surface) do |s|
run = fetch_run("best running shoes for flat feet", s)
hosts = run[:answers].flat_map { |a| a[:sources].map { |src| URI(src[:url]).host } }
[s, Set.new(hosts)]
end
end
threads.map(&:value).each do |surface, hosts|
puts "#{surface} cites: #{hosts.to_a.join(', ')}"
endWhat the Ruby API gives you
AgentGEO is a thin answer-access layer, not another closed dashboard. The API delivers the raw material; your Ruby script, Sidekiq worker or Rails service (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
surfacesarray. - Citations and sources, structured - every answer carries
sources[]with title, URL and position, ready to store, rank or diff. No Nokogiri, no regexing links out of markup. - Managed-scraper engine, maintained for you - answers come through supported access paths, so there is no browser 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 - run allowances by tier, priced by usage and never per seat.
Common problems with DIY answer-scraping in Ruby
The DIY route in Ruby is Ferrum, Watir or Capybara pointed at a real site instead of your app: drive ChatGPT or Perplexity headlessly and scrape the DOM. It demos fast and then turns into an operations problem.
- Browser drivers outside the test suite. Capybara and Watir exist to test your app; aiming them at ChatGPT means running headless Chrome in production, where a flaky driver isn't a red spec - it's missing data.
- selenium-webdriver version drift. The browser, the driver and the gem all update on their own schedules; a mismatch that surfaces at 2am is a class of failure a stateless POST simply doesn't have.
- Nokogiri against hydrated SPAs. Perplexity and AI Overviews render answers with client-side JS and hashed class names; your CSS/XPath selectors return
nilafter a silent frontend deploy, and you find out from empty columns. - Sidekiq jobs babysitting browsers. A background job holding a Chrome session ties up a worker for the whole 40-90 second scrape; scale that across six engines and you're sizing your queue around a browser pool.
- Proxy and CAPTCHA churn. Staying unblocked means residential proxies and CAPTCHA solving wired into your jobs - recurring cost and code that has nothing to do with your product.
| DIY scraping in Ruby | AgentGEO API | |
|---|---|---|
| Setup | Ferrum/Selenium, Chrome, proxy rotation | net/http + a key - standard library only |
| Parsing | Nokogiri against hashed class names | Stable JSON, identical across engines |
| Citations | Hand-rolled extraction per engine | Structured answers[].sources[] |
| Background jobs | Workers pinned to browser sessions | A quick HTTP call any worker can make |
| Adding an engine | A new scraper and driver setup | One more key in the surfaces array |
AgentGEO is the answer-access layer; the ranking, alerting or dashboards you build in Ruby 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 Ruby 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 Rails teams adding AI-visibility tracking to an existing product - a service object called from an ActiveJob that fans a keyword set across six engines and writes citations to Postgres is an afternoon of Ruby - 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 see the answers on your own brand? Get a free audit → · Read the 5-minute quickstart →
Get my free audit