> ## Documentation Index
> Fetch the complete documentation index at: https://docs.olostep.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Track AI search engines

> Call /v1/batches with dedicated parsers to extract structured answers, citations, ads, and shopping data from Google AI Mode, ChatGPT, Perplexity, Gemini, Copilot, and Google AI Overview.

Use [/batches](/api-reference/batches/create) plus a GEO/AEO (Generative Engine Optimization / Answer Engine Optimization) parser to query an AI search engine at scale and get structured JSON back: the answer, citations, and commercial surfaces such as ads, shopping cards, and product modules.

This page covers how to call `/v1/batches` and compact peeks of the JSON you get back.

```mermaid theme={null}
flowchart LR
  create["POST /v1/batches"] --> wait["Poll or webhook"]
  wait --> items["GET /v1/batches/id/items"]
  items --> retrieve["GET /v1/retrieve"]
  retrieve --> json["json_content"]
```

## Engines

Each engine needs its own parser ID and URL template. Pass the parser once at batch level. Encode the query in the URL.

| Engine             | Parser                                | URL template                                    | Credits | Max items |
| ------------------ | ------------------------------------- | ----------------------------------------------- | ------: | --------: |
| Google AI Mode     | `@olostep/google-aimode-results`      | `https://www.google.com/aimode?q={query}`       |       3 |      2500 |
| Google AI Overview | `@olostep/google-ai-overview-results` | `https://www.google.com/search?q={query}`       |       3 |      2500 |
| ChatGPT            | `@olostep/chatgpt-results`            | `https://chatgpt.com/?q={query}`                |       5 |      2500 |
| Perplexity         | `@olostep/perplexity-results`         | `https://www.perplexity.ai/?q={query}`          |       3 |      2500 |
| Gemini             | `@olostep/gemini-results`             | `https://gemini.google.com/?q={query}`          |       3 |      2500 |
| Microsoft Copilot  | `@olostep/microsoft-copilot-results`  | `https://copilot.microsoft.com/chats?q={query}` |       3 |      1000 |

All six support geo-targeting via the batch `country` parameter (ISO 3166-1 alpha-2). Country coverage differs by parser — fetch the live list rather than hardcoding it:

```bash theme={null}
curl "https://api.olostep.com/v1/countries?service=batches&parser=@olostep/google-aimode-results"
```

Use an informational prompt (`what is mitochondria`) when you care about citations. Use a commercial prompt (`best wireless headphones under $200`) when you need ads, shopping cards, or product modules — those surfaces are intent-detected and omitted when the engine does not render them.

## 1. Create the batch

`custom_id` must be unique within the batch. `country` is applied to every item.

<CodeGroup>
  ```python Python theme={null}
  from olostep import Olostep
  from urllib.parse import quote_plus

  client = Olostep(api_key="YOUR_API_KEY")

  queries = [
      "what is mitochondria",
      "best wireless headphones under $200",
  ]

  batch = client.batches.create(
      urls=[
          {
              "custom_id": f"aimode-{i}",
              "url": f"https://www.google.com/aimode?q={quote_plus(q)}",
          }
          for i, q in enumerate(queries, start=1)
      ],
      parser="@olostep/google-aimode-results",
      country="US",
  )

  print(batch.id, batch.status)
  ```

  ```javascript Node theme={null}
  import Olostep from 'olostep'

  const client = new Olostep({ apiKey: 'YOUR_API_KEY' })

  const queries = [
    'what is mitochondria',
    'best wireless headphones under $200',
  ]

  const batch = await client.batches.create(
    queries.map((q, i) => ({
      customId: `aimode-${i + 1}`,
      url: `https://www.google.com/aimode?q=${encodeURIComponent(q)}`,
    })),
    {
      parser: '@olostep/google-aimode-results',
      country: 'US',
    }
  )

  console.log(batch.id, batch.status)
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.olostep.com/v1/batches" \
    -H "Authorization: Bearer $OLOSTEP_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "parser": { "id": "@olostep/google-aimode-results" },
      "country": "US",
      "items": [
        {
          "custom_id": "aimode-1",
          "url": "https://www.google.com/aimode?q=what%20is%20mitochondria"
        },
        {
          "custom_id": "aimode-2",
          "url": "https://www.google.com/aimode?q=best%20wireless%20headphones%20under%20%24200"
        }
      ]
    }'
  ```
</CodeGroup>

To target a different engine, keep the same `items` shape and swap `parser.id` plus the host in each URL. One batch = one parser.

<Note>
  Processing time is roughly constant regardless of batch size (typically 5–8 minutes). Pass [`webhook`](/api-reference/common/webhooks) on create to get `batch.completed` instead of polling. New accounts are limited to 100 items per batch — contact [info@olostep.com](mailto:info@olostep.com) to raise it.
</Note>

## 2. Wait for completion

Poll [GET /v1/batches/\{batch\_id}](/api-reference/batches/info) until `status` is `completed`, or handle the [`webhook`](/api-reference/common/webhooks).

<CodeGroup>
  ```python Python theme={null}
  import time
  import requests

  headers = {"Authorization": f"Bearer {API_KEY}"}

  def wait_for_batch(batch_id):
      while True:
          info = requests.get(
              f"https://api.olostep.com/v1/batches/{batch_id}",
              headers=headers,
          ).json()
          if info["status"] == "completed":
              return info
          time.sleep(15)
  ```

  ```javascript Node theme={null}
  async function waitForBatch(batchId) {
    while (true) {
      const res = await fetch(`https://api.olostep.com/v1/batches/${batchId}`, {
        headers: { Authorization: `Bearer ${process.env.OLOSTEP_API_KEY}` },
      })
      const info = await res.json()
      if (info.status === 'completed') return info
      await new Promise((r) => setTimeout(r, 15000))
    }
  }
  ```

  ```bash cURL theme={null}
  curl "https://api.olostep.com/v1/batches/batch_abc123" \
    -H "Authorization: Bearer $OLOSTEP_API_KEY"
  ```
</CodeGroup>

The Python and Node SDKs can wait for you: iterate `batch.items()` / `for await (const item of batch.items())` and they block until the batch finishes.

## 3. Retrieve `json_content`

List items, then retrieve JSON for each `retrieve_id`. The GEO payload is the parsed object inside `json_content` (a JSON string — parse it).

<CodeGroup>
  ```python Python theme={null}
  import json
  import requests

  headers = {"Authorization": f"Bearer {API_KEY}"}

  items = requests.get(
      f"https://api.olostep.com/v1/batches/{batch_id}/items",
      headers=headers,
  ).json()["items"]

  for item in items:
      payload = requests.get(
          "https://api.olostep.com/v1/retrieve",
          headers=headers,
          params={"retrieve_id": item["retrieve_id"], "formats": "json"},
      ).json()
      data = json.loads(payload["json_content"])
      print(item["custom_id"], data.get("prompt"), len(data.get("sources") or []))
  ```

  ```javascript Node theme={null}
  const itemsRes = await fetch(
    `https://api.olostep.com/v1/batches/${batchId}/items`,
    { headers: { Authorization: `Bearer ${process.env.OLOSTEP_API_KEY}` } }
  )
  const { items } = await itemsRes.json()

  for (const item of items) {
    const params = new URLSearchParams({
      retrieve_id: item.retrieve_id,
      formats: 'json',
    })
    const payload = await fetch(
      `https://api.olostep.com/v1/retrieve?${params}`,
      { headers: { Authorization: `Bearer ${process.env.OLOSTEP_API_KEY}` } }
    ).then((r) => r.json())

    const data = JSON.parse(payload.json_content)
    console.log(item.custom_id, data.prompt, (data.sources || []).length)
  }
  ```

  ```python Python SDK theme={null}
  for item in batch.items():
      content = item.retrieve(["json"])
      print(item.custom_id, content.json_content)
  ```
</CodeGroup>

Failed items are listed separately — pass `status=failed` on [GET /v1/batches/\{batch\_id}/items](/api-reference/batches/items). Hosted JSON is also available at `json_hosted_url` on the retrieve payload for about 7 days.

## What you get back

Every parser returns `answer_markdown`. Most also return `prompt`. The rest is engine-specific. Compact peeks below are from live `country=US` batches.

<Note>
  The engines listed here are the public GEO parsers. We also support additional parsers and connectors internally, plus custom parsers for specific sites — we share those on request. Email [info@olostep.com](mailto:info@olostep.com) or reach out on [Slack](https://olostep-users.slack.com/join/shared_invite/zt-2bfddyi8h-JzfjOgavg~98DJ1om1B5Lg#/shared-invite/email).
</Note>

<Tabs>
  <Tab title="Google AI Mode">
    Citations are in `sources` (`cited: true` when referenced inline). Structured sections are in `text_blocks` (`text`, `heading`, `list`, `table`). Sponsored cards are in `ads` when Google shows them — a live headphones query returned sources and a comparison table but omitted `ads`. Empty fields are stripped, not returned as `[]`.

    ```json theme={null}
    {
      "answer_markdown": "When looking for the best wireless over-ear or on-ear headphones under $200...",
      "sources": [
        {
          "url": "https://www.reddit.com/r/HeadphoneAdvice/comments/173hvu2/best_wireless_headphones_for_under_200_dollars/",
          "title": "Best wireless headphones for under 200 dollars : r/HeadphoneAdvice",
          "description": "According to a Reddit user, the Sennheiser Accentum headphones are a good option...",
          "domain": "https://www.reddit.com",
          "cited": false
        }
      ],
      "text_blocks": [
        { "type": "text", "snippet": "When looking for the best wireless over-ear or on-ear headphones under $200..." },
        {
          "type": "table",
          "snippet": "Model | Best For | Key Features | Price Range",
          "data": {
            "headers": ["Model", "Best For", "Key Features", "Price Range"],
            "rows": [
              ["Anker Soundcore Space Q45", "Overall Value & ANC", "Strong adaptive ANC, 50-hour battery life", "~$100 - $150"]
            ]
          }
        }
      ]
    }
    ```
  </Tab>

  <Tab title="ChatGPT">
    Inline chips are `inline_references`. The sources drawer is `sources`. Shopping is `products`. Sponsored brand blocks are `ads` (`brand` + `cards`). Empty modules are `[]`. `network_search_calls` is the web-search trace.

    ```json theme={null}
    {
      "url": "https://chatgpt.com/?q=best%20wireless%20headphones%20under%20%24200",
      "prompt": "best wireless headphones under $200",
      "answer_markdown": "If you mean **over-ear wireless headphones with ANC**...",
      "inline_references": [
        {
          "url": "https://www.rtings.com/headphones/reviews/best/by-price/under-200",
          "text": "The 5 Best Headphones Under $200 of 2026 - RTINGS.com",
          "position": 1
        }
      ],
      "sources": [
        {
          "url": "https://www.tomsguide.com/us/best-headphones-deals,news-28645.html",
          "title": "Best headphone deals for August 2026",
          "snippet": "This August 2026 headphone deal roundup...",
          "cited": false,
          "date_published": "2026-08-19T16:18:38.000Z",
          "attribution": "www.tomsguide.com"
        }
      ],
      "ads": [
        {
          "brand": { "name": "Razer", "url": "https://www.razer.com/" },
          "cards": [
            {
              "title": "Razer BlackShark V3 Pro",
              "body": "Pros swear by its unrivaled clarity...",
              "url": "https://www.razer.com/gaming-headsets/razer-blackshark-v3-pro?utm_source=chatgpt&...",
              "image": "https://bzrcdn.openai.com/fe8bef58c7129858.jpg"
            }
          ]
        }
      ],
      "products": [
        {
          "title": "Soundcore Space Q45",
          "price": 139.99,
          "currency": "$",
          "vendors": [
            {
              "price": 139.99,
              "currency": "$",
              "website": "Best Buy",
              "link": "https://www.bestbuy.com/product/soundcore-by-anker-space-q45-...?utm_source=chatgpt.com"
            }
          ]
        }
      ],
      "locations": [],
      "web_searched": true,
      "network_search_calls": {
        "search_triggered": true,
        "model_slug": "auto",
        "search_queries": [
          { "query": "best wireless headphones under $200 2026 Sony WH-CH720N Soundcore Space Q45...", "type": "model_query" }
        ]
      }
    }
    ```
  </Tab>

  <Tab title="Perplexity">
    `shopping_cards`, `videos`, `images`, `hotels`, and `places` are empty arrays when that module was not shown. `search_model_queries` is what Perplexity actually searched.

    ```json theme={null}
    {
      "url": "https://www.perplexity.ai/search/53051455-01ac-48a2-8aa7-45e518588af0",
      "prompt": "best wireless headphones under $200",
      "answer_markdown": "*   Anker Soundcore Space Q45 Wireless...",
      "sources": [
        {
          "position": 1,
          "label": "The 5 Best Headphones Under $200 of 2026",
          "url": "https://www.rtings.com/headphones/reviews/best/by-price/under-200",
          "description": "",
          "domain": "www.rtings.com",
          "date": ""
        }
      ],
      "related_queries": [
        "best budget wireless headphones under 200 dollars for sound quality"
      ],
      "shopping_cards": [],
      "videos": [],
      "images": [],
      "hotels": [],
      "places": [],
      "search_model_queries": [
        { "query": "best wireless headphones under $200", "engine": "web", "limit": 8 }
      ],
      "model": "perplexity",
      "web_searched": true
    }
    ```
  </Tab>

  <Tab title="Google AI Overview">
    `text_blocks` is the overview broken into paragraphs and lists. `sources` are the AIO citations. `organic_results` is the classic SERP underneath, when present.

    ```json theme={null}
    {
      "url": "https://www.google.com/search?q=what+is+mitochondria&gl=us",
      "prompt": "what is mitochondria",
      "answer_markdown": "mitochondria\n\nMitochondria are membrane-bound organelles...",
      "sources": [
        {
          "title": "The Mitochondria",
          "url": "https://www.youtube.com/watch?v=sl6RXHnAMVs&t=7",
          "source": "YouTube",
          "index": 1
        }
      ],
      "text_blocks": [
        { "type": "paragraph", "snippet": "mitochondria\n\nMitochondria are membrane-bound organelles..." }
      ],
      "organic_results": [
        {
          "position": 1,
          "title": "Mitochondria",
          "link": "https://www.genome.gov/genetics-glossary/Mitochondria",
          "snippet": "Mitochondria are membrane-bound cell organelles..."
        }
      ]
    }
    ```
  </Tab>

  <Tab title="Gemini">
    ```json theme={null}
    {
      "url": "https://gemini.google.com/app/d11cddf049763b9b",
      "prompt": "best wireless headphones under $200",
      "answer_markdown": "Finding great wireless headphones under $200...",
      "sources": [
        {
          "position": 1,
          "label": "Best Noise Cancelling Headphones under $200 (50+ Tested!)",
          "url": "https://recordingnow.com/blog/best-budget-wireless-headphones/",
          "description": "The Sony WH-CH720N is the LIGHTEST full-sized...",
          "confidence_level": 9
        }
      ],
      "links_attached": true,
      "model": "gemini"
    }
    ```
  </Tab>

  <Tab title="Microsoft Copilot">
    `sources[].position` is a character offset into `answer_markdown`, not a rank. `cited` is whether the source is attached in the answer.

    ```json theme={null}
    {
      "url": "https://copilot.microsoft.com/chats?q=best%20wireless%20headphones%20under%20%24200",
      "prompt": "best wireless headphones under $200",
      "answer_markdown": "**The best wireless headphones under $200 in 2026 are the Sony WH-CH720N...",
      "sources": [
        {
          "url": "https://progressiveradionetwork.com/best-wireless-headphones-under-200-dollars/",
          "title": "10 Best Wireless Headphones Under 200$ (August 2026) Tested",
          "position": 1124,
          "icon_url": "https://services.bingapis.com/favicon?url=progressiveradionetwork.com",
          "cited": true
        }
      ]
    }
    ```
  </Tab>
</Tabs>

## Related

* [Create Batch](/api-reference/batches/create)
* [Batch items](/api-reference/batches/items)
* [Retrieve content](/api-reference/retrieve)
* [Webhooks](/api-reference/common/webhooks)
