Skip to content
API4 min read2026-07-09

PHP AI Visibility API

Query the raw answers ChatGPT, Perplexity, Google AI Overviews, Google AI Mode, Copilot and Gemini actually give - from PHP, with the curl extension you already have. 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 that json_decode turns into a plain array. No Panther, no headless Chrome, no long-running browser fighting PHP's request lifecycle. 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 PHP, with the bundled curl extension and json_decode. No symfony/panther, no headless Chrome to keep alive between requests: one POST, and the answer text, citations and sources come back as an associative array your controllers and jobs can act on.

Call the AI Visibility API from PHP

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 the curl extension that ships with PHP - no Composer package required. (Want the payload before writing code? The full response shape is in the docs.)

php
<?php

$ch = curl_init('https://api.agentgeo.org/v1/fetches');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ag_live_your_key_here',
        'Content-Type: application/json',
    ],
    // surfaces: chatgpt | perplexity | gemini | google_ai_overview | google_ai_mode | copilot
    CURLOPT_POSTFIELDS     => json_encode([
        'query'    => 'best running shoes for flat feet',
        'surfaces' => ['chatgpt'],
    ]),
]);

$body = curl_exec($ch);
if ($body === false) {
    throw new RuntimeException('agentgeo: ' . curl_error($ch));
}
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status !== 200) {
    throw new RuntimeException("agentgeo: unexpected status $status");
}

$run = json_decode($body, true);
foreach ($run['answers'] as $answer) {
    echo $answer['surfaceKey'], ' → ', $answer['answerText'], PHP_EOL;
    foreach ($answer['sources'] as $src) { // cited sources, in order
        echo '  ', $src['position'], ' ', $src['title'], ' ', $src['url'], PHP_EOL;
    }
}

The response is a plain JSON object - json_decode($body, true) hands you a clean associative array, no HTML to scrub:

json
{
  "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 array walk covers chatgpt, perplexity, gemini, google_ai_overview, google_ai_mode and copilot. Ask for all six in one call and diff the citation sets - that diff is the core of any GEO/AEO analysis you'd want to build.

Wrap the call in a small function and you can ask every engine at once - the API returns one answer per surface, so a single request covers all six. Here it collects the cited host per surface:

php
<?php

/** Fetch one query across several engines, return cited hosts per surface. */
function fetch_cited_hosts(string $query, array $surfaces): array
{
    $ch = curl_init('https://api.agentgeo.org/v1/fetches');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'Authorization: Bearer ag_live_your_key_here',
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS     => json_encode(compact('query', 'surfaces')),
    ]);
    $run = json_decode(curl_exec($ch), true);
    curl_close($ch);

    $hosts = [];
    foreach ($run['answers'] as $answer) {
        foreach ($answer['sources'] as $src) {
            $hosts[$answer['surfaceKey']][] = parse_url($src['url'], PHP_URL_HOST);
        }
    }
    return $hosts;
}

// All six engines in one call
$hosts = fetch_cited_hosts('best running shoes for flat feet', [
    'chatgpt', 'perplexity', 'gemini', 'google_ai_overview', 'google_ai_mode', 'copilot',
]);
print_r($hosts);

Prefer Guzzle? It works identically - $client->post('https://api.agentgeo.org/v1/fetches', ['headers' => [...], 'json' => [...]]) returns the same JSON, and everything below is unchanged.

What the PHP API gives you

AgentGEO is a thin answer-access layer, not another closed dashboard. The API delivers the raw material; your PHP app, queue worker or scheduled command (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 surfaces array.
  • Citations and sources, structured - every answer carries sources[] with title, URL and position, ready to store, rank or diff. No DOMDocument, no XPath, no regexing links out of markup.
  • Managed-scraper engine, maintained for you - answers come through supported access paths, so there is no browser process to supervise, no proxy list to rotate and no IP ban to recover from mid-run.
  • Provider metadata - every response carries surfaceKey and providerFields, 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-mcp package, 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 PHP

The DIY route in PHP is symfony/panther or chrome-php/chrome: drive ChatGPT or Perplexity in a headless browser and scrape the DOM. It works on your laptop and then collides with everything PHP is good at.

  • Headless browsers vs the request lifecycle. PHP is built to handle a request and exit; a Panther-driven Chrome wants to live for minutes. On shared or FPM hosting that mismatch means timeouts, zombie chrome processes and workers you have to reap by hand.
  • max_execution_time and memory limits. A 40-90 second scrape blows past default execution limits, and a headless Chrome per query pushes memory_limit - so you end up tuning php.ini around a browser instead of shipping features.
  • DOMDocument and XPath against hydrated React. ChatGPT and Perplexity render answers client-side with hashed class names; your XPath selectors start returning empty nodes after a frontend deploy you never see, usually in production.
  • No stable citation model. Pulling which sources an answer cited - in order, deduplicated - out of rendered HTML is fiddly and engine-specific, and you reinvent it for each of six engines.
  • cron + proxy churn. Keeping scrapers alive means residential proxies, retries and CAPTCHA solving wired into your scheduler - operational work that has nothing to do with your product.
DIY scraping in PHPAgentGEO API
SetupPanther/chrome-php, Chrome binary, proxiesThe bundled curl extension + a key
ParsingDOMDocument/XPath against hashed classesStable JSON, identical across engines
CitationsHand-rolled extraction per engineStructured answers[].sources[]
HostingLong-running browser vs FPM lifecycleA plain HTTPS call - shared hosting fine
Adding an engineA new scraper and browser sessionOne more key in the surfaces array

AgentGEO is the answer-access layer; the ranking, alerting or dashboards you build in PHP on top of it are yours. You own the raw data and ship it inside your own app 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 PHP app - Claude Code, Cursor, Codex or any MCP client - one command wires it up, and the agent pulls answers and runs the GEO analysis itself:

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

Who builds on this

Mostly Laravel and WordPress teams adding AI-visibility tracking to an existing product - a queued job that fans a keyword set across six engines and writes citations to MySQL is an afternoon of PHP - 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

FAQ

Direct answers to the questions this page raises.

No. AgentGEO's managed scrapers run server-side, so the whole integration is a curl POST to https://api.agentgeo.org/v1/fetches with a Bearer ag_live_ key. There is no Chrome to launch, no browser process to reap and no proxy pool to rotate - the failure modes of browser automation never enter your codebase.

Yes. It is a single outbound HTTPS request with the curl extension every PHP host bundles, so there is no long-running browser to fight the FPM request lifecycle, no max_execution_time blowups from a headless scrape and no memory_limit pressure from a Chrome per query. If your host can call an API, it can call this one.

Yes - they are interchangeable here. $client->post('https://api.agentgeo.org/v1/fetches', ['headers' => ['Authorization' => 'Bearer ag_live_...'], 'json' => ['query' => '...', 'surfaces' => ['chatgpt']]]) returns the same JSON. Use whichever your project already depends on; the response handling is identical.

In Laravel, wrap the call in a service class and run it from a queued job or a scheduled Artisan command - Http::withToken('ag_live_...')->post(...) works out of the box. In WordPress, wp_remote_post() with the same headers and body does the job inside a cron event. Either way you get the same structured records back.

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 surfaces array - no new scraper, no new parser.

Keep reading

Where this page leads next.

Your brand, next

Run these checks on your own brand

Send a URL for a free human-run audit, or connect your agent over MCP and run the same records yourself.