# Google AI Mode ads schema Source: https://cloro.dev/docs/api-reference/endpoint/aimode/ads Schema for sponsored ad blocks returned by the Google AI Mode endpoint, including merchant branding, product carousel cards, pricing, and ad metadata. This section documents the **ads** data returned by the [Google AI Mode endpoint](/docs/api-reference/endpoint/monitor-aimode): the sponsored ad section extracted when AI Mode surfaces sponsored shopping results, part of the AI Mode response so no separate API call is needed. For use cases, pricing context, and copy-paste examples, see the [AI Mode shopping API](https://cloro.dev/ai-mode/shopping/) page on the product site. Ads in AI Mode **`ads` is an object, not an array.** AI Mode wraps sponsored results in an `AdSection` object with a section `title` and an `ads` array of individual ad items. This differs from other providers (e.g., [ChatGPT ads](/docs/api-reference/endpoint/chatgpt/ads)) where `ads` is a flat array — check the wrapper shape when parsing. ## Example request Ads are intent-detected; no flag is required. ```json theme={null} { "prompt": "best 3D printers for hobbyists", "country": "US" } ``` ## AdSection structure | Field | Type | Description | | ------- | ------ | ------------------------------------------------------------------------- | | `title` | string | Section title for the ads (e.g., "Here are some 3D printers to consider") | | `ads` | array | Array of sponsored [ad items](#ad-item-structure) | ## Ad item structure | Field | Type | Description | | ---------- | ------- | ---------------------------------------------- | | `title` | string | Ad product title | | `url` | string | Ad click-through URL | | `position` | integer | Position index of the ad | | `price` | object | Structured pricing with `value` and `currency` | | `store` | string | Merchant/store name | | `rating` | number | Product rating | | `reviews` | string | Review count | ### `price` | Field | Type | Description | | ---------- | -------------- | ----------------------------------------------------------------------------------------------- | | `value` | number \| null | Parsed numeric price. `null` when the visible text can't be unambiguously parsed. | | `currency` | string \| null | Currency symbol (e.g. `"$"`, `"£"`, `"R$"`, `"€"`). `null` when no symbol was found. | | `raw` | string | Visible ad text verbatim (e.g. `"$149.99"`). Always present when the parser had any input text. | See the [inline products docs](/docs/api-reference/endpoint/aimode/inline-products#price-and-oldprice) for the full price-shape contract; it's shared across all AI Mode price-bearing fields. ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are some 3D printers to consider...", "ads": { "title": "Here are some 3D printers to consider", "ads": [ { "title": "Bambu Lab X2D", "url": "https://www.google.com/aclk?sa=L&ai=abc", "position": 1, "price": { "value": 899.0, "currency": "$" }, "store": "Bambu Lab US", "rating": 4.7, "reviews": "584" } ] } } } ``` # Google AI Mode citation pills schema Source: https://cloro.dev/docs/api-reference/endpoint/aimode/citation-pills Schema for inline citation pills returned by the Google AI Mode endpoint, with each cited source attached to the visible pill chip it appears on. This section documents the **citationPills** data returned by the [Google AI Mode endpoint](/docs/api-reference/endpoint/monitor-aimode). Google renders inline pill chips (e.g. `[Reddit +3]`) next to AI Mode text to attribute each claim to one or more sources. The `result.citationPills` array exposes those pills denormalized, as part of the AI Mode response so no separate API call is needed: each entry is one **(pill, source)** pair carrying a per-source `label` (the source's own page title), a `citationPillId` that groups entries from the same chip, and the per-source `url`/`domain`/`description`/`position`. For use cases, pricing context, and copy-paste examples, see the [AI Mode citations API](https://cloro.dev/ai-mode/sources/) page on the product site. When a pill cites N sources, the array contains N entries sharing the same `citationPillId` but carrying different per-source `label`, `url`, and `domain`. Group by `citationPillId` to recover the pill-level structure. The field is omitted from `result` when the answer has no pills. ## Example request ```json theme={null} { "prompt": "best laptops for programming", "country": "US" } ``` ## Citation pill structure | Field | Type | Description | | ---------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `label` | string | Per-source title from the sources rail (e.g. `"Best laptops for programmers in 2026"`). Always present; may be an empty string when the rail has no title for this source — read `domain` / `url` for source identity in that case. | | `citationPillId` | integer | Stable identifier shared by all entries from the same visible chip. 1-based ordinal assigned in document order. | | `url` | string | Direct URL of the cited source. | | `domain` | string | Host extracted from `url`, for grouping and display. | | `description` | string | Source snippet from the sources rail when Google ships one. Omitted when absent. | | `position` | integer | 1-based position of this source in the sibling [`result.sources`](/docs/api-reference/endpoint/aimode/sources) array. | ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are several laptops for programming...", "sources": [ { "position": 1, "url": "https://example.com/best-laptops-2026", "label": "Best laptops for programmers in 2026", "description": "A guide to the top laptops for software development this year." }, { "position": 2, "url": "https://example.com/dev-laptops-review", "label": "Developer laptop reviews", "description": "Reviews of laptops aimed at software developers." }, { "position": 3, "url": "https://example.com/macbook-pro-review", "label": "MacBook Pro for developers", "description": "Why the MacBook Pro is a popular pick for developers." } ], "citationPills": [ { "label": "Best laptops for programmers in 2026", "citationPillId": 1, "url": "https://example.com/best-laptops-2026", "domain": "example.com", "description": "A guide to the top laptops for software development this year.", "position": 1 }, { "label": "MacBook Pro for developers", "citationPillId": 1, "url": "https://example.com/macbook-pro-review", "domain": "example.com", "description": "Why the MacBook Pro is a popular pick for developers.", "position": 3 } ] } } ``` # Google AI Mode inline products schema Source: https://cloro.dev/docs/api-reference/endpoint/aimode/inline-products Schema for individual product references embedded inline in the Google AI Mode response text, including titles, prices, merchants, and offer links. This section documents the **inline products** data returned by the [Google AI Mode endpoint](/docs/api-reference/endpoint/monitor-aimode): product cards found within the AI response text, part of the AI Mode response so no separate API call is needed. Unlike [shopping cards](/docs/api-reference/endpoint/aimode/shopping-cards) — which group multiple products into a dedicated carousel — inline products are stand-alone product references embedded directly in the AI's prose. Both arrays may coexist in a single response. For use cases, pricing context, and copy-paste examples, see the [AI Mode shopping API](https://cloro.dev/ai-mode/shopping/) page on the product site. Inline products in AI Mode ## Example request Inline products are intent-detected; no flag is required. ```json theme={null} { "prompt": "what are the best wireless headphones for travel", "country": "US" } ``` ## Inline product structure | Field | Type | Description | | ------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `title` | string | Product title | | `position` | integer | 1-indexed rank across the `inlineProducts` array in DOM/render order. Always emitted. | | `price` | object | Structured pricing — see [price object](#price-and-oldprice) for the shape | | `oldPrice` | object | Original price before discount (same shape as `price`) | | `store` | string | Merchant/store name (e.g. `"Amazon"`, `"Mercado Livre"`) | | `thumbnail` | string | Product image URL. May be a `https://` URL or a `data:image/jpeg;base64,...` blob injected by Google's client JS. Omitted when the page was captured before Google's image loader ran. | | `productLink` | string | Direct product URL | ### `price` and `oldPrice` | Field | Type | Description | | ---------- | -------------- | ------------------------------------------------------------------------------------------------------------------ | | `value` | number \| null | Parsed numeric price. `null` when the visible text can't be unambiguously parsed. | | `currency` | string \| null | Currency symbol (e.g. `"$"`, `"£"`, `"R$"`, `"€"`). `null` when no symbol was found. | | `raw` | string | Visible card text verbatim (e.g. `"$329.99"`, `"R$ 4,40/mês"`). Always present when the parser had any input text. | The `raw` field captures the price string exactly as Google rendered it, which covers three cases: * **Parseable** — `{value, currency, raw}` all populated. Use `value` for math/aggregation. * **Installment** — Brazilian and other locales render monthly-payment offers like `R$ 4,40/mês`. We emit `{raw}` only (no `value`) because shipping `4.40` as the product price would be wrong. Fall back to `raw` for display. * **Unparseable / locale-ambiguous** — e.g. `"Free"`, `"€1.234"` in an unknown locale context. We emit whatever was extractable (often just `raw`) so you can render the card text without losing the signal. ## Response example ```json theme={null} { "success": true, "result": { "text": "Based on expert reviews, here are the top wireless headphones for travel...", "inlineProducts": [ { "title": "Sony WH-1000XM5", "position": 1, "price": { "value": 298.0, "currency": "$", "raw": "$298.00" }, "oldPrice": { "value": 399.99, "currency": "$", "raw": "$399.99" }, "store": "Amazon", "thumbnail": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/...", "productLink": "https://www.google.com/shopping/product/example" }, { "title": "Short Floral Estampa Floral - Compre Já", "position": 2, "price": { "raw": "R$ 4,40/mês" }, "store": "Mercado Livre", "productLink": "https://www.google.com/shopping/product/example2" } ] } } ``` # Google AI Mode map schema Source: https://cloro.dev/docs/api-reference/endpoint/aimode/map Schema for GPS-enriched location data returned by the Google AI Mode endpoint when map-aware results are surfaced, including coordinates, names, and addresses. This section documents the **map** data returned by the [Google AI Mode endpoint](/docs/api-reference/endpoint/monitor-aimode): GPS-enriched location data extracted when AI Mode surfaces map-aware results, part of the AI Mode response so no separate API call is needed. Each entry includes latitude/longitude coordinates suitable for plotting on a map. Map entries appear in render order; array index `0` is the first card shown on the map carousel. For use cases, pricing context, and copy-paste examples, see the [AI Mode places API](https://cloro.dev/ai-mode/places/) page on the product site. Map in AI Mode For inline place cards without GPS coordinates, see [places](/docs/api-reference/endpoint/aimode/places). `places` and `map` may both appear in the same response — `places` is the inline place-card layer, `map` is the GPS-enriched layer. ## Example request Map entries are intent-detected; no flag is required. ```json theme={null} { "prompt": "coffee shops near Central Park", "country": "US" } ``` ## Map entry structure | Field | Type | Description | | ----------------- | ------- | ------------------------------------------------ | | `title` | string | Place name | | `link` | string | Google viewer URL for the place | | `placeId` | string | Google Places ID | | `index` | integer | Position index of the place in the results | | `gps_coordinates` | object | Geographic coordinates (`latitude`, `longitude`) | | `thumbnail` | string | Place thumbnail image URL | | `rating` | number | Star rating (0-5) | | `reviews` | integer | Number of reviews | | `type` | string | Place type or category | | `address` | string | Full address | | `status` | string | Operating status (e.g., "Open now") | ### `gps_coordinates` | Field | Type | Description | | ----------- | ------ | -------------------- | | `latitude` | number | Latitude coordinate | | `longitude` | number | Longitude coordinate | ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are some coffee shops near Central Park...", "map": [ { "title": "Central Park Cafe", "link": "https://www.google.com/viewer/place?mid=/g/11bw3yq9kp", "placeId": "/g/11bw3yq9kp", "index": 0, "gps_coordinates": { "latitude": 40.7831, "longitude": -73.9712 }, "thumbnail": "https://lh5.googleusercontent.com/p/example", "rating": 4.5, "reviews": 1234, "type": "Coffee shop", "address": "123 Broadway, New York", "status": "Open now" } ] } } ``` # Google AI Mode places schema Source: https://cloro.dev/docs/api-reference/endpoint/aimode/places Schema for inline place cards returned by the Google AI Mode endpoint when location intent is detected, including business names, ratings, and addresses. This section documents the **places** data returned by the [Google AI Mode endpoint](/docs/api-reference/endpoint/monitor-aimode): inline place cards extracted when AI Mode detects location intent, part of the AI Mode response so no separate API call is needed. Places appear in the order Google AI Mode ranks them in the answer; array index `0` is the top result. For use cases, pricing context, and copy-paste examples, see the [AI Mode places API](https://cloro.dev/ai-mode/places/) page on the product site. Places in AI Mode For GPS-enriched location data with latitude and longitude coordinates, see [map](/docs/api-reference/endpoint/aimode/map). `places` and `map` may both appear in the same response. ## Example request Places are intent-detected; no flag is required. ```json theme={null} { "prompt": "best ramen restaurants in Tokyo", "country": "JP" } ``` ## Place structure | Field | Type | Description | | ------------ | ------- | ---------------------------------------------------- | | `title` | string | Place name | | `link` | string | Google viewer URL for the place | | `placeId` | string | Google Places ID | | `index` | integer | Position index of the place in the results | | `thumbnail` | string | Place thumbnail image URL | | `rating` | number | Star rating (0-5) | | `reviews` | integer | Number of reviews | | `type` | string | Place type or category | | `priceLevel` | string | Price level indicator (e.g., "\$", "\$\$", "\$\$\$") | | `address` | string | Full address | | `status` | string | Operating status (e.g., "Open now") | ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are highly-rated ramen restaurants in Tokyo...", "places": [ { "title": "Gion District", "link": "https://www.google.com/viewer/place?mid=/g/11bw3yq9kp", "placeId": "/g/11bw3yq9kp", "index": 0, "thumbnail": "https://lh5.googleusercontent.com/p/example", "rating": 4.5, "reviews": 2900, "type": "Tourist attraction", "priceLevel": "$$", "address": "Gionmachi, Higashiyama Ward, Kyoto", "status": "Open now" } ] } } ``` # Google AI Mode product results schema Source: https://cloro.dev/docs/api-reference/endpoint/aimode/product-results Schema for expanded merchant offers behind each Google AI Mode product cluster: direct URLs, prices, installments, stock, and delivery badges. Product results are the merchant offers behind each product cluster in a [Google AI Mode](/docs/api-reference/endpoint/monitor-aimode) response — merchant URL, price, installment terms, and stock, delivery and returns badges. Set `include.expandProducts` to get them as `result.productResults`. ```json theme={null} { "prompt": "best wireless headphones under $200", "country": "US", "include": { "expandProducts": true } } ``` Without the flag, products are reported at cluster level only: [shopping cards](/docs/api-reference/endpoint/aimode/shopping-cards) and [inline products](/docs/api-reference/endpoint/aimode/inline-products) carry a headline price and a link to Google's product viewer. Expansion opens that viewer and returns the offers inside it. Both arrays are unchanged either way. Product viewer opened from an AI Mode product Same shape as [Google Search product results](/docs/api-reference/endpoint/google/product-results), where the panel is already in the SERP and needs no flag. ## Limits and cost Each entry is one product cluster, deduplicated across `shoppingCards` and `inlineProducts`, and costs one extra fetch of Google's product viewer: * **+1 credit** per entry returned, charged after the scrape completes * **6 clusters** expanded per scrape at most, so the surcharge tops out at +6 * clusters that fail to fetch are dropped from the array, and not charged See [providers](/docs/guides/providers#ai-mode-additional-features) for the pricing table. ## Product result structure Each entry describes one product cluster and its merchant offers. | Field | Type | Description | | ----------------- | ------ | ------------------------------------------------------------------------- | | `title` | string | Product cluster title, as Google displayed it | | `stores` | array | Merchant offers. See [store structure](#store-structure) | | `aboutTheProduct` | object | `description` (Google's prose blurb) and `features` (bullets, when shown) | | `specifications` | object | Spec tables grouped by category. See [specifications](#specifications) | | `images` | array | `mainUrl` and `thumbnailUrl` per image, often identical | Only `title` is guaranteed — anything Google's panel omits is left out of the object rather than sent as `null`. Panels can also carry `brand`, `rating`, `reviews`, `priceRange`, `typicalPrices`, `variants`, `relatedProducts`, `userReviews`, `videos`, `discussionsAndForums` and `highlights`, emitted under those names when present, though most carry none of them. ### Specifications A category name mapped to a flat set of spec key/value pairs: ```json theme={null} { "specifications": { "general": { "Brand": "Sennheiser", "Noise cancellation": "Yes" } } } ``` Category names and spec keys arrive in the **response language** — a `country: "MX"` request returns `{"general": {"Marca": "Sennheiser"}}`. Iterate the object; don't read a hard-coded key like `specifications.general.Brand`. ### Store structure | Field | Type | Description | | --------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | Merchant name (e.g. `"Amazon"`) | | `link` | string | Merchant product URL, passed through from Google verbatim | | `price` | object | See [price shape](#price-shape). Omitted entirely when the displayed text can't be parsed | | `installments` | string | Installment terms as displayed (e.g. `"for 24 mo, $0 now, $798.96 total"`) | | `description` | string | Merchant's listing title, naming the exact SKU sold (e.g. `"iPhone 17 256GB White - Apple"`) — unlike `title`, which names the product overall | | `buyingOptions` | array | Merchant badges in display order — stock status, rating, delivery promise, returns window. Google varies these per merchant and locale, so they stay an unparsed list of strings | | `logo` | string | Merchant logo URL | | `shipping` | string | Shipping cost or promise as displayed | | `condition` | string | Item condition (e.g. `"New"`, `"Refurbished"`) | | `rating` | number | Merchant rating | | `reviews` | string | Merchant review count, abbreviated (e.g. `"384"`, `"2.3k"`) | Expect the first six on most offers and the rest only occasionally. Everything beyond `name` is optional and omitted when absent — Google shows different combinations per merchant, so an "Out of stock" listing may have no price or link at all. `link` is not validated against the cluster. Google occasionally serves a merchant URL for an unrelated SKU, so an offer's link can point at a different product than its `description` names. Verify against `description` if link correctness matters. ### Price shape | Field | Type | Description | | ---------- | ------ | --------------------------------------------------------------------------------------------------------------- | | `value` | number | Parsed numeric price; always present when `price` is emitted. On installment offers this is the monthly payment | | `currency` | string | ISO 4217 code (e.g. `"USD"`). Unrecognized symbols fall through as the glyph itself (e.g. `"R$"`) | | `raw` | string | Visible price text verbatim (e.g. `"$149.99"`) | `currency` here is an **ISO code** (`"USD"`), while [shopping cards](/docs/api-reference/endpoint/aimode/shopping-cards#price-and-oldprice) and [inline products](/docs/api-reference/endpoint/aimode/inline-products#price-and-oldprice) carry the raw symbol (`"$"`). Normalize before comparing across surfaces. ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are highly-rated wireless headphones under $200...", "shoppingCards": [ { "title": "Sony WH-CH720N Noise Cancelling Headphones", "position": 1, "price": { "value": 149.99, "currency": "$", "raw": "$149.99" }, "store": "Amazon" } ], "productResults": [ { "title": "Sony WH-CH720N Noise Cancelling Headphones", "aboutTheProduct": { "description": "Wireless noise-canceling headphones with up to 35-hour battery life and multipoint connection." }, "specifications": { "general": { "Brand": "Sony", "Noise cancellation": "Yes", "Wireless technology type": "Bluetooth" } }, "images": [ { "mainUrl": "https://encrypted-tbn3.gstatic.com/shopping?q=tbn:ANd9Gc...", "thumbnailUrl": "https://encrypted-tbn3.gstatic.com/shopping?q=tbn:ANd9Gc..." } ], "stores": [ { "name": "Amazon", "link": "https://www.amazon.com/sony-wh-ch720n", "price": { "value": 149.99, "currency": "USD", "raw": "$149.99" }, "description": "Sony WH-CH720N Wireless Noise Canceling Headphones, Black", "shipping": "Free delivery by Thu, Aug 14", "condition": "New", "rating": 4.6, "reviews": "2.3k", "buyingOptions": [ "In stock online", "4.6/5", "Free delivery", "30-day returns" ] }, { "name": "Best Buy", "link": "https://www.bestbuy.com/site/sony-wh-ch720n", "price": { "value": 33.33, "currency": "USD", "raw": "$33.33" }, "installments": "for 6 mo, $0 now, $199.98 total", "description": "Sony - WH-CH720N Wireless Noise Cancelling Headphones - Black", "buyingOptions": [ "In stock online", "Free shipping" ] } ] } ] } } ``` Note the two `currency` conventions in the same payload: `shoppingCards[].price.currency` is the raw symbol (`"$"`), while `productResults[].stores[].price.currency` is an ISO code (`"USD"`). # Google AI Mode shopping cards schema Source: https://cloro.dev/docs/api-reference/endpoint/aimode/shopping-cards Schema for shopping product cards returned by the Google AI Mode endpoint when shopping intent is detected, with pricing, ratings, offers, and reviews. This section documents the **shopping cards** data returned by the [Google AI Mode endpoint](/docs/api-reference/endpoint/monitor-aimode): product information extracted when AI Mode detects shopping intent, part of the AI Mode response so no separate API call is needed. For use cases, pricing context, and copy-paste examples, see the [AI Mode shopping API](https://cloro.dev/ai-mode/shopping/) page on the product site. Shopping cards in AI Mode For individual product references embedded inline within the AI response text (rather than in a dedicated carousel), see [inline products](/docs/api-reference/endpoint/aimode/inline-products). Both arrays may appear in the same response. ## Example request Shopping cards are intent-detected; no flag is required. ```json theme={null} { "prompt": "best wireless headphones under $200", "country": "US" } ``` ## Shopping card structure | Field | Type | Description | | --------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------- | | `title` | string | Product title | | `position` | number | 1-indexed rank across the `shoppingCards` array in DOM/render order. | | `price` | object | Structured pricing with `value`, `currency`, and `raw` | | `store` | string | Merchant/store name (conditionally populated — may be absent) | | `rating` | number | Product rating | | `reviews` | string | Review count in Google's abbreviated format (e.g., `"2.3k"`, `"1m"`). Parse as a string; convert client-side if you need a numeric value. | | `thumbnail` | string | Product image URL | | `productLink` | string | Direct product URL (conditionally populated — may be absent) | | `oldPrice` | object | Original price before discount (conditionally populated — absent when no discount) | | `snippet` | string | Product description snippet | | `snippet_links` | array | Links found within the product snippet | `store`, `productLink`, and `oldPrice` are conditionally populated — not every item includes all fields. Build your pipeline to handle missing fields on any individual shopping card. ### `price` and `oldPrice` | Field | Type | Description | | ---------- | -------------- | ------------------------------------------------------------------------------------------------------------------ | | `value` | number \| null | Parsed numeric price. `null` when the visible text can't be unambiguously parsed. | | `currency` | string \| null | Currency symbol (e.g. `"$"`, `"£"`, `"R$"`, `"€"`). `null` when no symbol was found. | | `raw` | string | Visible card text verbatim (e.g. `"$149.99"`, `"R$ 4,40/mês"`). Always present when the parser had any input text. | The `raw` field captures the price string exactly as Google rendered it. Use it as a fallback display when `value` is `null` (installment offers, locale-ambiguous numerics) — see the [inline products docs](/docs/api-reference/endpoint/aimode/inline-products#price-and-oldprice) for the full price-shape contract; it's shared across all AI Mode price-bearing fields. ### `snippet_links` | Field | Type | Description | | ------ | ------ | ----------- | | `text` | string | Link text | | `link` | string | Link URL | ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are highly-rated wireless headphones under $200...", "shoppingCards": [ { "title": "Sony WH-CH720N Noise Cancelling Headphones", "position": 1, "price": { "value": 149.99, "currency": "$", "raw": "$149.99" }, "oldPrice": { "value": 199.99, "currency": "$", "raw": "$199.99" }, "store": "Amazon", "rating": 4.5, "reviews": "2.3k", "thumbnail": "https://example.com/product.jpg", "productLink": "https://www.amazon.com/sony-wh-ch720n", "snippet": "Noise-canceling wireless headphones with 30-hour battery life", "snippet_links": [ { "text": "noise canceling", "link": "https://www.google.com/search?q=noise+canceling" } ] } ] } } ``` # Google AI Mode sources schema Source: https://cloro.dev/docs/api-reference/endpoint/aimode/sources Schema for source citations returned by the Google AI Mode endpoint, listing URLs, titles, snippets, and publishers referenced in the generated answer. This section documents the **sources** data returned by the [Google AI Mode endpoint](/docs/api-reference/endpoint/monitor-aimode): the citations behind the answer, part of the AI Mode response so no separate API call is needed. They follow the [common sources structure](/docs/guides/making-requests/sync#sources-array-structure) (`position`, `url`, `label`, and `description`) with no AI-Mode-specific fields. For use cases, pricing context, and copy-paste examples, see the [AI Mode citations API](https://cloro.dev/ai-mode/sources/) page on the product site. Sources in AI Mode ## Example request ```json theme={null} { "prompt": "best laptops for programming", "country": "US" } ``` ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are several laptops for programming...", "markdown": "Here are several laptops for programming...[Best laptops for programmers in 2026](https://example.com/best-laptops-2026)[Developer laptop reviews](https://example.com/dev-laptops-review)", "sources": [ { "position": 1, "url": "https://example.com/best-laptops-2026", "label": "Best laptops for programmers in 2026", "description": "A guide to the top laptops for software development this year." }, { "position": 2, "url": "https://example.com/dev-laptops-review", "label": "Developer laptop reviews", "description": "Reviews of laptops aimed at software developers." } ] } } ``` # Google AI Mode videos schema Source: https://cloro.dev/docs/api-reference/endpoint/aimode/videos Schema for inline video cards embedded within the Google AI Mode response, including titles, thumbnails, durations, channels, and source links. This section documents the **videos** data returned by the [Google AI Mode endpoint](/docs/api-reference/endpoint/monitor-aimode): inline video cards Google embeds inside an AI Mode answer, part of the AI Mode response so no separate API call is needed. In observed traffic these are YouTube watch links, returned with the title, channel, duration, and a public thumbnail. The `link` is the canonical watch URL with timestamp parameters stripped. Videos appear in the order Google AI Mode embeds them in the answer; array index `0` is the first card shown. For use cases, pricing context, and copy-paste examples, see the [Google AI Mode scraper](https://cloro.dev/ai-mode/) page on the product site. Videos in AI Mode ## Example request Videos are intent-detected; no flag is required. ```json theme={null} { "prompt": "best 3d printer to buy", "country": "US" } ``` ## Video structure | Field | Type | Description | | ----------- | ------ | ----------------------------------------------------------- | | `title` | string | Video title | | `link` | string | Canonical watch URL (timestamp parameters stripped) | | `platform` | string | Hosting platform parsed from the card byline (e.g. YouTube) | | `channel` | string | Channel or uploader name | | `duration` | string | Visible duration label (`mm:ss` or `hh:mm:ss`) | | `thumbnail` | string | Public thumbnail URL | ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are some highly-rated 3D printers to consider...", "videos": [ { "title": "The BEST 3D Printer for YOU - Don't Buy the Wrong One!", "link": "https://www.youtube.com/watch?v=g0DZnQdI-xQ", "platform": "YouTube", "channel": "3D Print Dood", "duration": "08:49", "thumbnail": "https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcS5Bntg3F0Y7P3YgTPgdyillyeCguV_YQJ99KFUmNYxmQtVt1f2" } ] } } ``` # ChatGPT sponsored ads schema Source: https://cloro.dev/docs/api-reference/endpoint/chatgpt/ads Schema for sponsored ad blocks extracted from ChatGPT responses, including advertiser branding, product carousel cards, pricing, and offer metadata. The [ChatGPT endpoint](/docs/api-reference/endpoint/monitor-chatgpt) returns sponsored **ads** in `result.ads` when you opt in with `include.ads: true` — no separate API call. For use cases, pricing context, and copy-paste examples, see the [ChatGPT ads scraper](https://cloro.dev/chatgpt-ads/) page on the product site. Ads in ChatGPT `result.ads` lists every ad OpenAI **serves** for the prompt; the interface renders at most one of them, so use the `rendered` field to tell which ad was actually shown. See [Served vs shown](#served-vs-shown) below. ## Example request ```json theme={null} { "prompt": "What are the best running shoes?", "model": "CHATGPT", "country": "US", "include": { "ads": true } } ``` ## Ad structure | Field | Type | Description | | ------------------ | ------- | -------------------------------------------------------------------------------------------------------- | | `brand` | object | Advertiser brand information (name, url, favicon) | | `cards` | array | Array of carousel cards promoting products/services | | `visibility` | string | OpenAI serving status: `allowed`, or `hidden` when the ad was filtered (for example, an integrity check) | | `adsResponseIndex` | integer | The ad's slot position in OpenAI's served ad response (served order, not a guarantee of what was shown) | | `rendered` | boolean | Whether the ad was shown as a visible card; `false` means served but not displayed | ## Brand structure | Field | Type | Description | | --------- | ------ | --------------------------------------- | | `name` | string | Advertiser brand name | | `url` | string | Advertiser brand URL with attribution | | `favicon` | string | Advertiser brand favicon URL (optional) | ## Card structure | Field | Type | Description | | ------- | ------ | ------------------------------------- | | `title` | string | Card title/heading | | `body` | string | Card description text | | `url` | string | Card destination URL with attribution | | `image` | string | Card image URL (optional) | ## Served vs shown The served list can include ads the user never saw. Three fields let you measure real exposure: * **`rendered`** — `true` only for the ad shown as a visible card. Count `rendered: true` for user-visible impressions rather than the length of `ads`. * **`visibility`** — `allowed` for eligible ads; `hidden` marks ads OpenAI filtered (for example, an integrity check). A `hidden` ad is never shown. * **`adsResponseIndex`** — the served slot position. It reflects the order OpenAI returned the ads, not which one was shown. It is usually zero-based, as in the example below, but not guaranteed — a lone ad can arrive at a higher index (for example `2`) — so treat it as an opaque position rather than a dense sequence. `rendered` is derived from the response DOM at capture time. It reliably flags served-but-not-shown ads, but treat it as a strong signal rather than a guarantee — a rare late-painting render can be missed. ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are several running shoes...", "ads": [ { "brand": { "name": "Acme Shoes", "url": "https://www.acmeshoes.com?utm_source=chatgpt.com", "favicon": "https://images.openai.com/favicon.ico" }, "cards": [ { "title": "Premium Running Shoes", "body": "Lightweight running shoes with cushioning technology", "url": "https://www.acmeshoes.com/running?utm_source=chatgpt.com", "image": "https://images.openai.com/product.jpg" }, { "title": "Trail Running Shoes", "body": "Durable shoes designed for off-road running with strong grip", "url": "https://www.acmeshoes.com/trail?utm_source=chatgpt.com", "image": "https://images.openai.com/product2.jpg" } ], "visibility": "allowed", "adsResponseIndex": 0, "rendered": true }, { "brand": { "name": "Trailblaze Gear", "url": "https://www.trailblazegear.com?utm_source=chatgpt.com", "favicon": "https://images.openai.com/favicon.ico" }, "cards": [ { "title": "All-Terrain Running Shoes", "body": "Grippy outsole built for mixed trails", "url": "https://www.trailblazegear.com/shoes?utm_source=chatgpt.com", "image": "https://images.openai.com/product3.jpg" } ], "visibility": "allowed", "adsResponseIndex": 1, "rendered": false } ] } } ``` # ChatGPT citation pills schema Source: https://cloro.dev/docs/api-reference/endpoint/chatgpt/citation-pills Schema for inline citation pills returned by the ChatGPT endpoint, with each cited source attached to the visible pill chip it appears on in the answer. ChatGPT cites sources inline as it answers. The [ChatGPT endpoint](/docs/api-reference/endpoint/monitor-chatgpt) exposes those citations denormalized in `result.citationPills` — no separate API call. For use cases, pricing context, and copy-paste examples, see the [ChatGPT sources API](https://cloro.dev/chatgpt/sources/) page on the product site. Each entry is one **(pill, source)** pair. When a chip cites N sources, the array contains N entries sharing the same `citationPillId` but carrying different per-source `label`, `url`, and `domain`. Group by `citationPillId` to recover the pill-level structure. The `markdown` field carries the same citations as inline `[label](url)` links; `text` carries plain text without them. The `citationPills` field is omitted from `result` when the answer has no pills. ## Example request ```json theme={null} { "prompt": "What is the best AI software?", "model": "CHATGPT", "country": "US" } ``` ## Citation pill structure | Field | Type | Description | | ---------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `label` | string | Per-source title of the citation. Always present; may be an empty string when the citation event ships no title — read `domain` / `url` for source identity in that case. | | `citationPillId` | integer | 0-based identifier shared by all entries from the same chip. **Note:** unlike the other providers (Google AIO, AI Mode, Copilot, Perplexity, Gemini) which use 1-based `citationPillId`, ChatGPT's IDs start at 0 for historical reasons. | | `url` | string | The URL of the citation source | | `domain` | string | Domain of the citation source (e.g., `"example.com"`) | | `description` | string, optional | Description of the citation content. **The key is omitted when ChatGPT provides no snippet** — see `type`. | | `position` | integer | 1-based position of this source in the sibling [`result.sources`](/docs/api-reference/endpoint/chatgpt/sources) array. | | `type` | string | *ChatGPT-only.* Discriminator: `"searchResult"` or `"groupedWebpage"`. See below for what this signals about `description`. | | `datePublished` | string, optional | *ChatGPT-only.* ISO 8601 date string of when the source was published. Omitted when the citation event ships no publish date — never emitted as `null`. | ### `type` values `type` reflects the structure of the underlying citation event in ChatGPT's response: * **`"searchResult"`** — a regular web search hit. ChatGPT provides a snippet, so `description` is populated. * **`"groupedWebpage"`** — a citation rendered in the ChatGPT UI as a multi-source card (commonly used for community references like Reddit, Wikipedia, and ArXiv). ChatGPT does not provide a per-source snippet for these, so **the `description` key is omitted entirely from the pill object**. When the same source URL appears as both a search result and inside a grouped card, the search-result form (with description) is preferred. ## Response example ```json theme={null} { "success": true, "result": { "text": "**ChatGPT** — Most versatile for writing, reasoning, and general problem-solving.\n**Claude** — Strong focus on safety and ethical reasoning.", "markdown": "**ChatGPT** — Most versatile for writing, reasoning, and general problem-solving. [Top 10 Best AI Apps in 2025](https://www.top10.com/best-lists/best-ai-apps)\n**Claude** — Strong focus on safety and ethical reasoning. [Best AI Platforms Compared](https://www.godofprompt.ai/blog/best-ai)", "citationPills": [ { "label": "Top 10 Best AI Apps in 2025", "citationPillId": 0, "url": "https://www.top10.com/best-lists/best-ai-apps", "domain": "top10.com", "description": "A guide to AI applications available today", "position": 1, "type": "searchResult", "datePublished": "2025-01-15" }, { "label": "Artificial intelligence — Wikipedia", "citationPillId": 1, "url": "https://en.wikipedia.org/wiki/Artificial_intelligence", "domain": "en.wikipedia.org", "position": 2, "type": "groupedWebpage" } ] } } ``` # ChatGPT entities schema Source: https://cloro.dev/docs/api-reference/endpoint/chatgpt/entities Schema for structured entity data extracted from ChatGPT responses, including products, brands, organizations, people, and concepts with descriptions. This section documents the **entities** data returned by the [ChatGPT endpoint](/docs/api-reference/endpoint/monitor-chatgpt): structured objects for the specific items, products, brands, or concepts ChatGPT identifies in its answer — like shopping cards but more general — part of the ChatGPT response so no separate API call is needed. Entities appear in the order ChatGPT references them; array index `0` is the first entity mentioned. For use cases, pricing context, and copy-paste examples, see the [ChatGPT brand mentions API](https://cloro.dev/chatgpt/entities/) page on the product site. ## Example request Entities are returned by default when ChatGPT mentions identifiable items; no flag is required. ```json theme={null} { "prompt": "What are the best sneakers under $100?", "model": "CHATGPT", "country": "US" } ``` ## Entity structure | Field | Type | Description | | ------ | ------ | ---------------------------------------------------- | | `type` | string | Entity type identifier (e.g., "product", "software") | | `name` | string | Entity name or title | ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are sneaker options under $100...", "entities": [ { "type": "product", "name": "adidas Grand Court Lo" }, { "type": "product", "name": "Reebok Club C 85 Vintage" }, { "type": "product", "name": "Nike Dunk Low Retro SE" } ] } } ``` # ChatGPT inline products schema Source: https://cloro.dev/docs/api-reference/endpoint/chatgpt/inline-products Individual product references with detailed rationale, themed reviews, and rendering hints for inline display in ChatGPT responses. Inline products are stand-alone product references embedded in ChatGPT's response text — displayed inline, in comparison tables, or as featured recommendations — unlike shopping cards, which group several products together. The [ChatGPT endpoint](/docs/api-reference/endpoint/monitor-chatgpt) returns them in `result.inlineProducts` when you opt in with `include.shopping: true` (+2 credits on the base cost). For use cases, pricing context, and copy-paste examples, see the [ChatGPT shopping API](https://cloro.dev/chatgpt/shopping/) page on the product site. Inline products in ChatGPT Inline products currently appear only when ChatGPT uses the `gpt-5-3` model. ChatGPT picks the model per request, so they may be absent from a response even with `include.shopping: true`. ## Example request ```json theme={null} { "prompt": "What are the best microwaves in the UK for 2026?", "model": "CHATGPT", "country": "GB", "include": { "shopping": true } } ``` ## Inline product structure | Field | Type | Description | | ----------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `product` | object | Complete product information (same structure as [shopping cards](/docs/api-reference/endpoint/chatgpt/shopping-cards)). Carries a `position` field — 1-indexed rank across the `inlineProducts` array, flat across the response. Always emitted. | | `render_as` | string | Rendering hint: "inline", "hero", or "block" | ## Response example ```json theme={null} { "success": true, "result": { "text": "Based on reviews and user feedback, here are the top microwaves...", "inlineProducts": [ { "product": { "id": "14461636969439553799", "title": "Sage Combi Wave 3-in-1 Microwave", "position": 1, "url": "https://www.johnlewis.com/sage-combi-wave-3-in-1?utm_source=chatgpt.com", "price": "£359.00", "merchants": "John Lewis & Partners + others", "image_urls": [ "https://images.openai.com/thumbnails/url/..." ], "num_reviews": 640, "rating": 4.2, "cite": "turn0product2", "offers": [ { "merchant_name": "John Lewis & Partners", "seller_name": "John Lewis & Partners", "product_name": "Sage Combi Wave 3-in-1 Microwave", "url": "https://www.johnlewis.com/sage-combi-wave-3-in-1?utm_source=chatgpt.com", "price": "£359.00", "details": "In stock, Free standard delivery", "original_price": "£399.00", "available": true, "checkoutable": false, "is_digital": null, "provider": "p2", "price_details": { "base": "£359.00", "total": "£359.00" }, "tag": { "text": "Best price" } } ] }, "render_as": "inline" } ] } } ``` # ChatGPT map entries schema Source: https://cloro.dev/docs/api-reference/endpoint/chatgpt/map Business and place information extracted from ChatGPT responses, including ratings, reviews, contact details, and location data from Yelp and Google. The [ChatGPT endpoint](/docs/api-reference/endpoint/monitor-chatgpt) returns **map entries** in `result.map` whenever ChatGPT surfaces business or place information from providers like **Yelp** and **Google Business** — no separate API call, no flag. For use cases, pricing context, and copy-paste examples, see the [ChatGPT scraper](https://cloro.dev/chatgpt/) page on the product site. Map entries in ChatGPT To also see the queries ChatGPT fired at the maps tool to build these entries, set `include.searchQueries: true` and read `result.mapSearchQueries`. These are separate from the web-search fan-out in `result.searchQueries`. **Cost:** Map entries are included at no extra cost — they're part of the base ChatGPT request (5 credits, plus the [+2 sync surcharge](/docs/guides/providers#sync-request-surcharge) if you use `/v1/monitor/*` instead of async). The number of businesses or per-business reviews returned does not change the price. See [Providers](/docs/guides/providers#chatgpt-additional-features) for the full ChatGPT pricing breakdown. ## Example request ```json theme={null} { "prompt": "best laundromats in Portland", "model": "CHATGPT", "country": "US" } ``` ## Map entry structure ### Core fields | Field | Type | Description | | ---------- | ------ | --------------------------------------------------------- | | `name` | string | Business name | | `id` | string | Business ID (Yelp ID or Google Place ID) | | `provider` | string | Data provider: "yelp", "b1"/"b3" (Google), or "yelp-feed" | | `position` | int | Position/ranking in results (added by parser) | ### Location fields | Field | Type | Description | | ---------------- | ------ | -------------------------------------------- | | `address` | string | Full street address | | `city` | string | City name | | `state` | string | State/province code | | `zipcode` | string | Postal/ZIP code | | `countryCode` | string | Country code (e.g., "US", "GB") | | `countryName` | string | Full country name | | `latitude` | float | GPS latitude coordinate | | `longitude` | float | GPS longitude coordinate | | `location` | string | Human-readable location string | | `distanceMeters` | float | Distance from user's location (if available) | ### Ratings and reviews | Field | Type | Description | | ------------------ | ------------------ | ------------------------------------ | | `rating` | float | Business rating (typically 0-5) | | `reviewCount` | int | Number of reviews | | `ratingScale` | int | Rating scale (usually 5) | | `reviews` | BusinessReview\[] | Array of detailed reviews | | `reviewHighlights` | ReviewHighlight\[] | Array of highlighted review snippets | ### Business info | Field | Type | Description | | --------------------- | ------ | -------------------------------------------- | | `categories` | array | Business categories (list) | | `description` | string | Business description | | `enrichedDescription` | string | Enhanced description with additional context | | `descriptionCite` | string | Citation/source for description | | `price` | int | Price level (1-4, number of \$ signs) | | `priceStr` | string | Price display: "\$", "\$\$", "\$\$\$", etc. | ### Contact and URLs | Field | Type | Description | | --------------------- | ---------------- | ------------------------------------ | | `phone` | string | Contact phone number | | `websiteUrl` | string | Business website | | `providerUrl` | string | Yelp/Google Business page URL | | `providerLogoUrl` | string | Provider logo (light theme) | | `providerLogoDarkUrl` | string | Provider logo (dark theme) | | `imageUrl` | string | Primary business image | | `imageUrls` | array | All business images | | `providerImages` | ProviderImage\[] | Provider-hosted images with metadata | | `yelpMenuUrl` | string | Yelp menu URL (Yelp only) | ### Hours and status | Field | Type | Description | | --------------------- | --------------- | ------------------------ | | `hours` | BusinessHour\[] | Array of operating hours | | `isOpen` | boolean | Currently open | | `isClosedPermanently` | boolean | Permanently closed | | `isClosedTemporarily` | boolean | Temporarily closed | | `nextOpenHour` | BusinessHour | Next opening time | | `specialHours` | BusinessHour\[] | Special/holiday hours | ### Additional fields | Field | Type | Description | | ---------------------- | ---------------------- | ------------------------------------------------------------------- | | `attributes` | object | Provider-specific attributes (parking, WiFi, payment methods, etc.) | | `tags` | array | Business tags | | `rank` | int | Business rank/position from provider | | `popularityScore` | float | Popularity score | | `isClaimed` | boolean | Whether business claimed their listing | | `dateOpened` | string | Date business opened | | `dateClosed` | string | Date business closed (if applicable) | | `reservationProviders` | ReservationProvider\[] | Available reservation providers | | `fromCache` | boolean | Whether data was cached | ### `BusinessHour` Used by: `hours`, `nextOpenHour`, `specialHours` | Field | Type | Description | | ------- | ------ | ----------------------------------------------------------------------------------- | | `day` | int | Day of week: 0-6 (0=Sunday, 1=Monday, ..., 6=Saturday) or 1-7 depending on provider | | `start` | string | Opening time in HHmm format (e.g., "0600" = 6:00 AM) | | `end` | string | Closing time in HHmm format (e.g., "2200" = 10:00 PM) | ### `BusinessReview` Used by: `reviews` | Field | Type | Description | | -------------- | ------ | ------------------------ | | `id` | string | Review ID from provider | | `url` | string | Review URL (may be null) | | `text` | string | Review text content | | `rating` | int | Review rating (1-5) | | `time_created` | string | ISO 8601 timestamp | ### `ReviewHighlight` Used by: `reviewHighlights` | Field | Type | Description | | -------------- | ------ | --------------------------------------------------------------- | | `photo` | string | URL to associated photo | | `sentence` | string | Review snippet with `[[HIGHLIGHT]]text[[ENDHIGHLIGHT]]` markers | | `review_count` | int | Number of reviews mentioning this highlight | ### `ProviderImage` Used by: `providerImages` | Field | Type | Description | | ------------------- | ------- | -------------------------------------------------- | | `caption` | string | Image caption | | `id` | string | Image ID | | `is_user_submitted` | boolean | Whether user uploaded | | `label` | string | Image category (e.g., "food", "inside", "outside") | | `large_url` | string | Large size image URL | | `medium_url` | string | Medium size image URL | | `original_url` | string | Original size image URL | | `url` | string | Standard image URL | | `slideshow_order` | int | Display order | ### `ReservationProvider` Used by: `reservationProviders` | Field | Type | Description | | -------------------------- | ------ | ---------------------------------------- | | `provider` | string | Service name (e.g., "yelp", "opentable") | | `provider_restaurant_id` | string | Restaurant ID on provider | | `provider_reservation_url` | string | Reservation URL | | `provider_name` | string | Display name | | `provider_logo` | string | Logo URL | | `launcherPayload` | any | Additional provider data | ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are highly-rated laundromats in Portland...", "map": [ { "id": "rVKID2OBQMyVpraqSjz9sg", "provider": "yelp", "name": "Spin Laundry Lounge", "position": 1, "address": "750 N Fremont St, Portland, OR 97227", "city": "Portland", "state": "OR", "zipcode": "97227", "countryCode": "US", "countryName": "United States", "latitude": 45.54764, "longitude": -122.67432, "location": "Portland, Oregon, United States", "rating": 4.2, "reviewCount": 381, "ratingScale": 5, "categories": ["Laundromat"], "price": 2, "priceStr": "$$", "phone": "+15032844794", "websiteUrl": "https://spinlaundrylounge.com", "providerUrl": "https://www.yelp.com/biz/spin-laundry-lounge-portland", "imageUrl": "https://s3-media0.fl.yelpcdn.com/bphoto/cOz0Qp5P3JgS1aLjVrR6_g/o.jpg", "imageUrls": [ "https://s3-media0.fl.yelpcdn.com/bphoto/cOz0Qp5P3JgS1aLjVrR6_g/o.jpg" ], "isOpen": true, "isClosedPermanently": false, "hours": [ {"day": 1, "start": "0700", "end": "2100"}, {"day": 2, "start": "0700", "end": "2100"}, {"day": 3, "start": "0700", "end": "2100"}, {"day": 4, "start": "0700", "end": "2100"}, {"day": 5, "start": "0700", "end": "2100"}, {"day": 6, "start": "0700", "end": "2100"}, {"day": 0, "start": "0700", "end": "2100"} ], "nextOpenHour": {"day": 2, "start": "0700", "end": "2100"}, "attributes": { "wi_fi": "free", "business_accepts_credit_cards": true, "business_parking": { "street": true, "lot": false } }, "fromCache": false } ] } } ``` Except for `name` and `position`, every field is optional — availability depends on the provider (Yelp vs Google) and on the business. Check for field existence before accessing. # ChatGPT shopping cards schema Source: https://cloro.dev/docs/api-reference/endpoint/chatgpt/shopping-cards Schema for structured shopping cards extracted from ChatGPT responses, including product titles, pricing, ratings, merchant offers, and commercial details. This section documents the **shopping cards** data returned by the [ChatGPT endpoint](/docs/api-reference/endpoint/monitor-chatgpt), extracted when ChatGPT returns product or commercial information. They are opt-in: set `include.shopping: true`, which adds +2 credits to the base cost. For use cases, pricing context, and copy-paste examples, see the [ChatGPT shopping API](https://cloro.dev/chatgpt/shopping/) page on the product site. ```json theme={null} { "prompt": "What are the best sneakers under $100?", "model": "CHATGPT", "country": "US", "include": { "shopping": true } } ``` Shopping cards in ChatGPT **Model availability** Shopping cards mostly appear when ChatGPT uses the `gpt-5-3-mini` model. ChatGPT selects the model per request, so cards may be absent even with the flag set. ## Shopping card structure Each shopping card contains: | Field | Type | Description | | ---------- | ----- | --------------------------------------------------------------------- | | `tags` | array | Category tags for the shopping card (e.g., \["electronics", "deals"]) | | `products` | array | Array of product information objects | **Category and attribute data** There is no dedicated `category` field or generic `attributes` bag. Category signals live in the card's `tags` (high-level, e.g. `["electronics", "deals"]`) and each product's `featured_tag` (product-level). Attributes are individual typed fields — `title`, `price`, `rating`, `merchants`, `offers[]` and so on. ## Product information Each product includes the following e-commerce data: | Field | Type | Description | | -------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `title` | string | Product name | | `position` | integer | 1-indexed rank across all products in all shopping cards in the response (flat, not reset per card). | | `url` | string | Product page URL with ChatGPT attribution | | `description` | string | Product description | | `price` | string | Current price (e.g., "\$57.00") | | `featured_tag` | string | Product category or style tag | | `merchants` | string | Merchant names (e.g., "Amazon.in + others") | | `image_urls` | array | Array of product image URLs | | `rating` | float | Product rating (0-5 scale) | | `num_reviews` | integer | Number of reviews | | `id` | string | Unique product identifier | | `cite` | string | Citation reference identifier (e.g., "turn0product0") | | `offers` | array | Array of shopping offers from different merchants | | `offers_see_more_boundary` | integer | Number of offers shown before "see more" | | `rating_grouped_citation` | object | Rating source information with supporting websites | | `showcase_metadata` | object | Product showcase display metadata with image positioning | | `providers` | array | List of data provider identifiers | | `product_lookup_key` | object | Key used to look up product updates | | `product_lookup_data` | object | Structured product lookup query data | | `show_price_disclosure` | boolean | Whether to show price disclosure | | `analytics_meta` | object | Analytics metadata with `product_event_uuid` | | `metadata_sources` | array | Sources of product metadata (e.g., \["p2"]) | | `generatedProductQuery` | string | Per-product shopping fan-out query: the broad natural-language query ChatGPT internally generated to source this product (e.g., "best microwaves uk 2026 countertop inverter combi microwave"). Useful for understanding the intent that surfaced the recommendation. | ## Offer information Each offer contains merchant-specific details. **Finding a product's brand** There is no top-level `brand` field. Brand is exposed as `offers[].brand`, but ChatGPT populates it on only a subset of offers, so it is frequently `null` or absent. When no offer carries it, parse `product.title` — the brand is usually the first token or two (e.g. `"Adidas VL Court 3.0"`). Fall back to `merchants` only when the merchant name matches the brand (e.g. `"adidas + others"`). | Field | Type | Description | | --------------------- | ------- | ------------------------------------------- | | `merchant_name` | string | Merchant name (e.g., "Amazon.in", "Macy's") | | `merchant_subtitle` | string | Additional merchant information | | `marketplace_seller` | string | Third-party seller on the marketplace | | `seller_name` | string | Seller name | | `brand` | string | Product brand | | `product_name` | string | Product name as listed by merchant | | `url` | string | Offer URL with ChatGPT attribution | | `price` | string | Offer price (e.g., "\$57.00") | | `details` | string | Stock and delivery information | | `original_price` | string | Original price before discount | | `available` | boolean | Offer availability status | | `checkoutable` | boolean | Whether offer can be checked out directly | | `checkout_payload` | string | Checkout payload data | | `checkout_image_urls` | array | Images for checkout flow | | `is_digital` | boolean | Whether product is digital | | `price_details` | object | Detailed price breakdown (see below) | | `tag` | object | Promotional tag (see below) | | `shop_id` | string | Shop identifier | | `provider` | string | Provider code (e.g., "p2") | ## Price details Offers include detailed price breakdown: | Field | Type | Description | | --------------- | ------ | ------------------------------------------ | | `display_price` | string | Formatted display price | | `base` | string | Base product price | | `shipping` | string | Shipping cost | | `tax` | string | Tax amount | | `total` | string | Total price including any additional costs | ## Offer tag Promotional tags on offers: | Field | Type | Description | | --------- | ------ | ----------------------------- | | `text` | string | Tag text (e.g., "Best price") | | `tooltip` | string | Tag tooltip text | ## Rating citation information Products include rating source attribution: | Field | Type | Description | | ---------------------- | ------ | -------------------------------------- | | `title` | string | Source title | | `url` | string | Source URL | | `supporting_websites` | array | Array of supporting website references | | `attribution` | string | Attribution text (may be null) | | `pub_date` | string | Publication date (may be null) | | `snippet` | string | Content snippet (may be null) | | `attribution_segments` | array | Attribution segments (may be null) | | `refs` | array | Reference objects | | `hue` | string | Color hue (may be null) | ### Supporting website Used by: `rating_grouped_citation.supporting_websites` | Field | Type | Description | | ---------- | ------ | ------------------------------ | | `title` | string | Supporting website title | | `url` | string | Supporting website URL | | `pub_date` | string | Publication date (may be null) | | `snippet` | string | Content snippet (may be null) | ## Showcase metadata Display metadata for product images, available on inline products: | Field | Type | Description | | ------------ | ------ | ------------------------------------------------- | | `image` | object | Image info: `url`, `width`, `height` | | `background` | object | Background colors: `type`, `primary`, `secondary` | | `slots` | object | Named display slots with positioning and fit data | ## Response example ```json theme={null} { "success": true, "result": { "text": "If you're shopping for sneakers under $100, here are some options...", "shoppingCards": [ { "tags": [ "stylish casual leather sneaker", "heritage retro leather sneaker", "canvas everyday skate-inspired" ], "products": [ { "title": "Adidas VL Court 3.0", "position": 1, "url": "https://www.adidas.com/us/vl-court-3.0-shoes/ID8797.html?utm_source=chatgpt.com", "price": "$57.00", "featured_tag": "stylish casual leather sneaker", "merchants": "adidas + others", "cite": "turn0product0", "image_urls": [ "https://images.openai.com/static-rsc-1/AY2CiYu1..." ], "id": "3250714974047560249", "rating": 4.7, "num_reviews": 10394, "show_price_disclosure": false, "offers_see_more_boundary": 3, "providers": ["product_info"], "metadata_sources": ["p2"], "offers": [ { "merchant_name": "adidas", "seller_name": "adidas", "product_name": "Adidas Women's VL Court 3.0", "url": "https://www.adidas.com/us/vl-court-3.0-shoes/ID8797.html?utm_source=chatgpt.com", "price": "$57.00", "details": "In stock online and nearby, Delivery between Sat - Mon $4.99", "original_price": null, "available": true, "checkoutable": false, "is_digital": null, "provider": "p2", "price_details": { "base": "$57.00", "total": "$57.00" }, "tag": { "text": "Best price" } } ] } ] } ] } } ``` # ChatGPT sources schema Source: https://cloro.dev/docs/api-reference/endpoint/chatgpt/sources Schema for source citations extracted from ChatGPT responses, including URLs, titles, snippets, publication dates, and footnote indicators. This section documents the **sources** data returned by the [ChatGPT endpoint](/docs/api-reference/endpoint/monitor-chatgpt): the references extracted from ChatGPT's sources modal, part of the ChatGPT response so no separate API call is needed. They follow the [common sources structure](/docs/guides/making-requests/sync#sources-array-structure) with additional ChatGPT-specific fields. For use cases, pricing context, and copy-paste examples, see the [ChatGPT sources API](https://cloro.dev/chatgpt/sources/) page on the product site. Sources in ChatGPT ChatGPT doesn't cite sources for every prompt. When web search is triggered but no sources are available, `sources` returns an empty array. ## Example request ```json theme={null} { "prompt": "What are the best AI recruiting tools?", "model": "CHATGPT", "country": "US" } ``` ## ChatGPT-specific source fields | Field | Type | Description | | --------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- | | `footnote` | boolean | Whether this source appears as a footnote (in a subsequent list in the sources modal) | | `datePublished` | string | The publication date of the source (e.g., "May 22, 2025"). Omitted when the source has no publish date — never emitted as `null`. | ## Response example ```json theme={null} { "success": true, "result": { "text": "Here's what I found about AI recruiting tools...", "sources": [ { "position": 1, "url": "https://www.rippling.com/blog/ai-recruiting?utm_source=chatgpt.com", "label": "12 Best AI Recruiting Tools For HR in 2025", "description": "Top AI recruiting tools that can help HR professionals streamline their hiring process...", "footnote": false, "datePublished": "May 22, 2025" }, { "position": 2, "url": "https://www.index.dev/blog/ai-recruiting-software-hiring-managers?utm_source=chatgpt.com", "label": "7 Best AI Recruiting Software for Hiring Managers in 2025", "description": "A guide to AI recruiting software options available for hiring managers...", "footnote": false, "datePublished": "April 10, 2025" }, { "position": 3, "url": "https://www.selectsoftwarereviews.com/buyer-guide/ai-recruiting?utm_source=chatgpt.com", "label": "10+ Best AI Recruiting Software for 2025: Reviews + Pricing", "description": "Reviews and pricing information for AI recruiting software solutions...", "footnote": true } ] } } ``` # Clear queue Source: https://cloro.dev/docs/api-reference/endpoint/clear-async-queue api-reference/openapi.json DELETE /v1/async/queue Delete every async task still in the QUEUED state in one call. Tasks already processing are left untouched, and queued tasks were never charged. Clears your organization's pending async queue in a single call — useful when you've enqueued a large backlog by mistake or want to reset the queue instead of waiting for it to drain. Only queued tasks are affected: * `QUEUED` tasks are **removed**. * `PROCESSING` tasks are already in-flight on a worker and are **left running** — they cannot be recalled from here. * `COMPLETED` and `FAILED` tasks are historical and are **left in place**; you can still retrieve them via [`GET /v1/async/task/{taskId}`](/docs/api-reference/endpoint/get-task-status) until they age out of the [retention window](/docs/api-reference/endpoint/get-task-status#task-retention-policy). Credits are only deducted once a task is processed, so clearing the queue has **no effect on your credit balance** and no refund is issued. The call is safe to repeat: an already-empty queue returns `cleared: 0` rather than an error. To check how many tasks are queued before clearing, use the [async status endpoint](/docs/api-reference/endpoint/get-async-status). ## Example usage ```bash cURL theme={null} curl -X DELETE "https://api.cloro.dev/v1/async/queue" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "success": true, "cleared": 42 } ``` # Microsoft Copilot citation pills schema Source: https://cloro.dev/docs/api-reference/endpoint/copilot/citation-pills Schema for inline citation pills returned by the Microsoft Copilot endpoint, with each cited source attached to the visible pill chip it appears on. This section documents the **citationPills** data returned by the [Microsoft Copilot endpoint](/docs/api-reference/endpoint/monitor-copilot). Copilot streams citation events alongside its answer text, and consecutive citation events are grouped into one visible chip. The `result.citationPills` array exposes those chips denormalized, as part of the Copilot response so no separate API call is needed: each entry is one **(pill, source)** pair carrying a per-source `label` (the source's own title from the citation event), a `citationPillId` that groups entries from the same chip, and the per-source `url`/`domain`/`description`/`position`. For use cases, pricing context, and copy-paste examples, see the [Copilot sources API](https://cloro.dev/copilot/sources/) page on the product site. When a pill cites N sources, the array contains N entries sharing the same `citationPillId` but carrying different per-source `label`, `url`, and `domain`. Group by `citationPillId` to recover the pill-level structure. The field is omitted from `result` when the answer has no pills. ## Example request ```json theme={null} { "prompt": "best laptops for programming", "country": "US" } ``` ## Citation pill structure | Field | Type | Description | | ---------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `label` | string | Per-source title from the citation event (e.g. `"Microsoft 365 Documentation"`). Always present; may be an empty string when the event ships no title — read `domain` / `url` for source identity in that case. | | `citationPillId` | integer | 1-based ordinal shared by all entries from the same chip. | | `url` | string | Direct URL of the cited source. | | `domain` | string | Host extracted from `url`, for grouping and display. | | `description` | string | Source snippet from the citation event when Copilot ships one. Omitted when absent. | | `position` | integer | 1-based position of this source in the sibling [`result.sources`](/docs/api-reference/endpoint/copilot/sources) array. | ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are several laptops for programming...", "sources": [ { "position": 1, "url": "https://docs.microsoft.com/", "label": "Microsoft 365 Documentation", "description": "Official development documentation" }, { "position": 2, "url": "https://github.com/microsoft/vscode", "label": "VS Code on GitHub", "description": "Visual Studio Code repository" } ], "citationPills": [ { "label": "Microsoft 365 Documentation", "citationPillId": 1, "url": "https://docs.microsoft.com/", "domain": "docs.microsoft.com", "description": "Official development documentation", "position": 1 }, { "label": "VS Code on GitHub", "citationPillId": 1, "url": "https://github.com/microsoft/vscode", "domain": "github.com", "description": "Visual Studio Code repository", "position": 2 } ] } } ``` # Microsoft Copilot map entries schema Source: https://cloro.dev/docs/api-reference/endpoint/copilot/map-entries Schema for business and place entries returned by the Microsoft Copilot endpoint, including names, ratings, reviews, photos, contact details, and addresses. This section documents the **map entries** data returned by the [Microsoft Copilot endpoint](/docs/api-reference/endpoint/monitor-copilot): business and place data auto-extracted from Copilot's local entity data (primarily Google), part of the Copilot response so no separate API call is needed. For use cases, pricing context, and copy-paste examples, see the [Copilot shopping API](https://cloro.dev/copilot/shopping/) page on the product site — it documents map entries alongside shopping cards. Map entries in Copilot ## Example request Map entries are returned by default when Copilot surfaces local business data; no flag is required. ```json theme={null} { "prompt": "theme parks with craft activities near Smoky Mountains", "country": "US" } ``` ## Map entry structure Each map entry contains business information using Copilot's native structure: | Field | Type | Description | | ------------- | ------- | --------------------------------------------------------- | | `name` | string | Business name | | `position` | integer | Position in results (1-indexed) | | `placeId` | string | Google Place ID | | `location` | object | Business location with `address`, `latitude`, `longitude` | | `phoneNumber` | string | Contact phone number | | `url` | string | Business website URL | | `reviews` | array | Review aggregations from providers | | `photos` | array | Business photos | | `openState` | string | Current open/closed status (e.g. "Open · Closes 9 PM") | | `category` | string | Business category (e.g. "Amusement park") | | `price` | any | Price level (if available) | | `layerLabel` | string | Map layer grouping label (e.g. "Theme Parks") | ### Location object | Field | Type | Description | | ----------- | ------ | ------------------- | | `address` | string | Full street address | | `latitude` | number | GPS latitude | | `longitude` | number | GPS longitude | ### Review object | Field | Type | Description | | ----------------- | ------- | ------------------------------- | | `count` | integer | Number of reviews | | `rating` | number | Average rating | | `providerName` | string | Review provider (e.g. "Google") | | `providerIconUrl` | string | Provider icon URL | | `url` | string | Reviews page URL | ### Photo object | Field | Type | Description | | -------------- | ------ | ---------------------- | | `url` | string | Photo URL | | `altText` | string | Alt text for the photo | | `providerName` | string | Photo provider name | | `providerUrl` | string | Photo provider URL | ## Response example ```json theme={null} { "success": true, "result": { "text": "Yes, the Smoky Mountains have several family-friendly theme parks...", "sources": [ { "position": 1, "label": "WonderWorks Pigeon Forge", "url": "http://www.wonderworksonline.com/pigeon-forge/", "description": null } ], "shoppingCards": [], "map": [ { "name": "WonderWorks Pigeon Forge", "position": 1, "placeId": "ChIJEYpTsk__W4gR2sQLGzOjE3o", "location": { "address": "100 Music Rd, Pigeon Forge, TN 37863", "latitude": 35.823257, "longitude": -83.5787498 }, "phoneNumber": "(865) 868-1800", "url": "http://www.wonderworksonline.com/pigeon-forge/", "reviews": [ { "count": 10625, "rating": 4.3, "providerName": "Google", "providerIconUrl": null, "url": "https://maps.google.com/?cid=8796553937077126362" } ], "photos": [ { "url": "https://lh3.googleusercontent.com/gps-cs-s/example", "altText": null, "providerName": null, "providerUrl": "https://maps.google.com" } ], "openState": "Open · Closes 9 PM", "category": "Amusement park", "price": null, "layerLabel": "Theme Parks" }, { "name": "Anakeesta", "position": 2, "placeId": "ChIJ0_BT8DxWWYgR4JxBzZpLjH4", "location": { "address": "576 Parkway, Gatlinburg, TN 37738", "latitude": 35.713083, "longitude": -83.5117751 }, "phoneNumber": "(865) 325-2400", "url": "https://www.anakeesta.com/", "reviews": [ { "count": 15738, "rating": 4.2, "providerName": "Google", "providerIconUrl": null, "url": "https://maps.google.com/?cid=9118746473759087840" } ], "photos": [ { "url": "https://lh3.googleusercontent.com/gps-cs-s/example", "altText": null, "providerName": null, "providerUrl": "https://maps.google.com" } ], "openState": "Open · Closes 8 PM", "category": "Theme park", "price": null, "layerLabel": "Theme Parks" } ] } } ``` **All fields are optional** Except for `name` and `position`, all fields in map entries are optional. Use optional chaining (`?.`) or check for field existence before accessing. Field availability depends on what data is available for each business. # Microsoft Copilot shopping cards schema Source: https://cloro.dev/docs/api-reference/endpoint/copilot/shopping-cards Schema for shopping product cards returned by the Microsoft Copilot endpoint, including product titles, pricing, ratings, merchant offers, and image URLs. This section documents the **shopping cards** data returned by the [Microsoft Copilot endpoint](/docs/api-reference/endpoint/monitor-copilot): product data automatically extracted when Copilot returns product or commercial information, part of the Copilot response so no separate API call is needed. For use cases, pricing context, and copy-paste examples, see the [Copilot shopping API](https://cloro.dev/copilot/shopping/) page on the product site. Shopping cards in Copilot ## Example request Shopping cards are returned by default when Copilot surfaces product data; no flag is required. ```json theme={null} { "prompt": "What are the best laptops for software development?", "country": "US" } ``` ## Shopping card structure | Field | Type | Description | | ---------- | ------ | --------------------------------------------- | | `type` | string | Shopping card type (e.g., "shoppingProducts") | | `layout` | string | Layout style (e.g., "Carousel") | | `products` | array | Array of detailed product information objects | ## Product information | Field | Type | Description | | ---------------- | ------- | ---------------------------------------------------------------------------------------------------- | | `product` | object | Product identifier with `id`, `groupId`, `brandGroupId` | | `position` | integer | 1-indexed rank across all products in all shopping cards in the response (flat, not reset per card). | | `offerId` | string | Unique offer identifier | | `url` | string | Product page URL | | `name` | string | Product name | | `description` | string | Product description | | `images` | array | Product images with `title` and `url` | | `specifications` | array | Product specs (e.g., Color, Size) with `displayName` and `values` | | `tags` | array | Product tags | | `price` | object | Price information with `amount`, `currency`, `currencySymbol` | | `discountPrice` | object | Discount price (same structure as `price`, may be null) | | `seller` | string | Seller name | | `sellerLogoUrl` | string | Seller logo URL | | `brandName` | string | Product brand name | | `rating` | object | Rating with `value` and `count` | | `canTrackPrice` | boolean | Whether price tracking is available | ## Product identifier structure | Field | Type | Description | | -------------- | ------ | ------------------------- | | `id` | string | Unique product ID | | `groupId` | string | Product group ID | | `brandGroupId` | string | Brand group ID (optional) | ## Price structure | Field | Type | Description | | ---------------- | ------ | ---------------------------- | | `amount` | number | Price amount | | `currency` | string | Currency code (optional) | | `currencySymbol` | string | Currency symbol (e.g., "\$") | ## Rating structure | Field | Type | Description | | ----------- | ------- | ---------------------------------- | | `value` | number | Rating value | | `count` | integer | Number of ratings | | `maxRating` | number | Maximum rating scale (may be null) | ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are several laptops for software development...", "shoppingCards": [ { "type": "shoppingProducts", "layout": "Carousel", "products": [ { "product": { "id": "prod_12345", "groupId": "group_12345" }, "position": 1, "offerId": "offer_12345", "url": "https://www.microsoft.com/en-us/d/surface-laptop-studio-2/8rqr54krf1dz", "name": "Microsoft Surface Laptop Studio 2", "description": "Surface laptop with high performance specs", "images": [ { "title": "Front view", "url": "https://example.com/surface.jpg" } ], "specifications": [ { "displayName": "Color", "values": ["Platinum", "Black"] } ], "tags": ["laptop", "professional"], "price": { "amount": 1999.99, "currency": "USD", "currencySymbol": "$" }, "seller": "Microsoft Store", "sellerLogoUrl": "https://example.com/microsoft-logo.png", "brandName": "Microsoft", "rating": { "value": 4.7, "count": 542 }, "canTrackPrice": true } ] } ], "sources": [ { "position": 1, "url": "https://www.microsoft.com/surface", "label": "Microsoft Surface", "description": "Official Microsoft Surface product information" } ] } } ``` # Microsoft Copilot sources schema Source: https://cloro.dev/docs/api-reference/endpoint/copilot/sources Schema for source citations returned by the Microsoft Copilot endpoint, listing URLs, titles, snippets, and publishers referenced in the generated answer. This section documents the **sources** data returned by the [Microsoft Copilot endpoint](/docs/api-reference/endpoint/monitor-copilot): the citations behind the answer, part of the Copilot response so no separate API call is needed. They follow the [common sources structure](/docs/guides/making-requests/sync#sources-array-structure) (`position`, `url`, `label`, and `description`) with no Copilot-specific fields. For use cases, pricing context, and copy-paste examples, see the [Copilot sources API](https://cloro.dev/copilot/sources/) page on the product site. Approximately 10% of prompts genuinely don't trigger Copilot to return citations, even after internal retries. An empty `result.sources` means Copilot chose not to cite, not that extraction failed — handle `sources: []` as a valid response. Sources in Copilot ## Example request ```json theme={null} { "prompt": "best laptops for programming", "country": "US" } ``` ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are several laptops for programming...", "sources": [ { "position": 1, "url": "https://example.com/best-laptops-2026", "label": "Best laptops for programmers in 2026", "description": "A guide to the top laptops for software development this year." }, { "position": 2, "url": "https://example.com/dev-laptops-review", "label": "Developer laptop reviews" } ] } } ``` # List of countries Source: https://cloro.dev/docs/api-reference/endpoint/countries api-reference/openapi.json GET /v1/countries Returns a list of all supported ISO 3166-1 alpha-2 country codes. Can be filtered by model to get countries available for specific AI providers. Returns the ISO 3166-1 alpha-2 country codes supported by the monitoring API, optionally filtered to one AI provider. ## Request parameters | Parameter | Type | Description | Example | | --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | | `model` | string | Optional. Filter countries available for a specific model. If not provided, returns all supported countries. An unrecognized value returns `400`. | `chatgpt` | **Available model values:** * `aimode` * `aioverview` * `chatgpt` * `copilot` * `gemini` * `google` * `perplexity` ## Example usage ### Basic request ```bash cURL theme={null} curl -X GET "https://api.cloro.dev/v1/countries" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} ["AF", "AX", "AL", "DZ", "AS", "AD"] ``` ### Filter by model ```bash cURL theme={null} curl -X GET "https://api.cloro.dev/v1/countries?model=chatgpt" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} ["US", "GB", "CA", "AU", "DE"] ``` ```bash cURL Python theme={null} import requests response = requests.get( "https://api.cloro.dev/v1/countries", params={"model": "perplexity"}, headers={"Authorization": "Bearer YOUR_API_KEY"} ) countries = response.json() print(countries) ``` ```bash cURL Node.js theme={null} const fetch = require('node-fetch'); const response = await fetch('https://api.cloro.dev/v1/countries?model=google', { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }); const countries = await response.json(); console.log(countries); ``` ## Caching The country list rarely changes, so fetch it when your application starts and cache it for 24 hours or more. Use the cached model-filtered list to populate your country picker and to validate input before a monitor request goes out, and keep a fallback in case the endpoint is briefly unavailable. ## Common questions ### Which countries are supported? cloro supports country-level targeting for nearly all countries. See the [request examples](#example-usage) above for how to query all countries or filter by specific provider. ### How do I target multiple countries or run a "global" query? `country` takes a single ISO 3166-1 alpha-2 code. There is no wildcard, "global", or multi-country value. To cover several markets, send one request per country and merge the results client-side — use [async tasks](/docs/guides/making-requests/async) or [batch tasks](/docs/api-reference/endpoint/create-batch-tasks) to fan out. ### Can I target specific cities or states? US state-level targeting is available on ChatGPT, Copilot, Perplexity, Gemini, and Grok via the `state` parameter. Use the [States endpoint](/docs/api-reference/endpoint/states) to get the full list of supported codes. The [Google Search](/docs/api-reference/endpoint/monitor-google) and [AI Mode](/docs/api-reference/endpoint/monitor-aimode) endpoints support city-level geo-targeting via the `location` parameter, which accepts [Google canonical location names](https://developers.google.com/google-ads/api/reference/data/geotargets). Google Search covers AI Overview too, since AI Overview is requested through it with `include.aioverview`. What's supported: * ✅ Country-level targeting using ISO 3166-1 alpha-2 codes (e.g., `US`, `GB`, `JP`) on all endpoints * ✅ State-level targeting via `state` parameter (e.g., `CA`, `NY`, `TX`) on ChatGPT, Copilot, Perplexity, Gemini, and Grok (US only for now) * ✅ City-level targeting via `location` parameter (e.g., `New York,New York,United States`) on Google Search (including AI Overview) and AI Mode What's not supported: * ❌ City-level targeting on Google News — it accepts `country` only, not `location` / `uule` * ❌ State-level targeting on Google Search or AI Mode (use `location` / `uule` instead) * ❌ State-level targeting outside the US * ❌ Metro/region targeting * ❌ Zip/postal code targeting * ❌ Latitude/longitude coordinates ### Why are my geo-targeted requests returning unexpected results? * Invalid country codes: use ISO 3166-1 alpha-2 codes (two-letter format). * Non-canonical `location` strings: `location` is not validated against Google's geotargets list. A string that isn't a canonical name is accepted and encoded as-is rather than rejected, and Google falls back to broader targeting — typically country level. You get a `200` with country-level results, not an error, so verify the exact canonical name (`City,Region,Country`) before assuming city targeting applied. * Provider limitations: coverage varies by provider and region. * Prompt language mismatch: English prompts in non-English countries may affect result quality. ### How does geo-targeting work? The `country` parameter routes your request through servers in or near the target region. What geo-targeting affects: * Search results and web sources * Local business information * Regional product availability (shopping cards) * Language and cultural context * Time zones and date formats What geo-targeting doesn't affect: * API pricing (same cost regardless of country) * Response format or structure * Available features or endpoints # Create async task Source: https://cloro.dev/docs/api-reference/endpoint/create-async-task api-reference/openapi.json POST /v1/async/task Submit an asynchronous task for background processing. Returns a task ID that you can use to poll for results or receive via webhook. Submits a task for asynchronous processing. The response carries a `taskId` you can poll via [`GET /v1/async/task/{taskId}`](/docs/api-reference/endpoint/get-task-status); if you provide a `webhook.url`, cloro also delivers the results to that URL. * **Priority**: optional, 1 (lowest, default) to 10 (highest). The scheduler runs higher [priority](/docs/guides/making-requests/async#request-prioritization) tasks first, then FIFO within the same level. Check your queue's priority distribution with the [async status endpoint](/docs/api-reference/endpoint/get-async-status). * **Idempotency**: an optional `idempotencyKey` prevents duplicate task creation. Reusing a key returns `409 Conflict`. New tasks start with status `QUEUED`. Submitting costs nothing on its own — credits are charged when the task completes. The balance is still checked at submission, so this endpoint returns `403 INSUFFICIENT_CREDITS` when your credits do not cover the task's `creditsToCharge`, and a queued task can still fail later if the balance runs out before the scheduler reaches it. See [what happens when credits run out](/docs/guides/making-requests/async#what-happens-to-async-tasks-when-my-credits-run-out). ## Example usage ```bash cURL theme={null} curl -X POST "https://api.cloro.dev/v1/async/task" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "taskType": "CHATGPT", "priority": 5, "idempotencyKey": "your-custom-identifier-123", "webhook": { "url": "https://your-app.com/webhook-handler" }, "payload": { "prompt": "What is the weather in New York?", "country": "US" } }' ``` ```json Response theme={null} { "success": true, "task": { "id": "b27a21e1-7c39-4aa2-a347-23e828c426f9", "taskType": "CHATGPT", "status": "QUEUED", "priority": 5, "createdAt": "2025-11-10T15:00:00.000Z", "latencyMs": null, "idempotencyKey": "your-custom-identifier-123" }, "credits": { "creditsToCharge": 10, "creditsCharged": 0 } } ``` # Create batch tasks Source: https://cloro.dev/docs/api-reference/endpoint/create-batch-tasks api-reference/openapi.json POST /v1/async/task/batch Submit up to 500 async tasks in one request. Each task is validated independently, so one invalid task does not block the rest. Returns per-task results. Submit up to **500 async tasks in a single HTTP request** instead of one API call per task. Each task accepts the same fields as a single async task: `taskType`, `payload`, and optionally `priority`, `idempotencyKey`, and `webhook`. * **Partial success**: each task is validated independently, so one invalid task does not block the others. * **Per-task results**: the response includes a `results` array with success or failure details for each task, preserving the original input order by `index`. * **Per-task webhook delivery**: each task fires its own webhook as soon as it completes — you don't wait for the full batch. Webhooks arrive in completion order, not submission order. For submitting a single task, see the [async requests guide](/docs/guides/making-requests/async). ## Request constraints | Constraint | Value | | ------------------------- | ----------------------------------------------------------------------------------------------------- | | Minimum tasks per request | 1 | | Maximum tasks per request | 500 | | Queue capacity check | All-or-nothing. The batch is rejected if it would exceed your organization's 100,000 task queue limit | The queue capacity check is **all-or-nothing** and runs before any task is processed: if the batch would exceed your queue limit, every task is rejected with a `429`. If the body is not a valid JSON array or is empty, you get a `422 Unprocessable Entity` instead, also before any task-level processing. ## Per-task error codes When a task fails validation within a batch, its result includes one of these error codes: | Code | Description | | ------------------------- | -------------------------------------------------------------------------------------------------- | | `VALIDATION_ERROR` | The task failed schema validation. `details` includes field-level errors. | | `RESOURCE_ALREADY_EXISTS` | The `idempotencyKey` was already used (either in a previous request or earlier in the same batch). | | `INSUFFICIENT_CREDITS` | Not enough credits remaining for this task. Credits are tracked per task within the batch. | ## Example usage ### Submit a batch of tasks ```bash cURL theme={null} curl -X POST "https://api.cloro.dev/v1/async/task/batch" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "CHATGPT", "priority": 5, "idempotencyKey": "batch-chatgpt-001", "webhook": { "url": "https://your-app.com/webhook-handler" }, "payload": { "prompt": "What do you know about Acme Corp?", "country": "US" } }, { "taskType": "PERPLEXITY", "priority": 3, "idempotencyKey": "batch-perplexity-001", "payload": { "prompt": "Latest news about Acme Corp", "country": "US" } }, { "taskType": "GEMINI", "payload": { "prompt": "Summarize Acme Corp recent announcements" } } ]' ``` ```javascript Node.js (axios) theme={null} import axios from 'axios'; const apiKey = 'YOUR_API_KEY'; const url = 'https://api.cloro.dev/v1/async/task/batch'; const tasks = [ { taskType: 'CHATGPT', priority: 5, idempotencyKey: 'batch-chatgpt-001', webhook: { url: 'https://your-app.com/webhook-handler' }, payload: { prompt: 'What do you know about Acme Corp?', country: 'US' }, }, { taskType: 'PERPLEXITY', priority: 3, idempotencyKey: 'batch-perplexity-001', payload: { prompt: 'Latest news about Acme Corp', country: 'US' }, }, { taskType: 'GEMINI', payload: { prompt: 'Summarize Acme Corp recent announcements' }, }, ]; try { const response = await axios.post(url, tasks, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, }); const { summary, results } = response.data; console.log(`Batch complete: ${summary.succeeded} succeeded, ${summary.failed} failed`); for (const result of results) { if (result.success) { console.log(`Task ${result.index}: ${result.task.id} (${result.task.taskType})`); } else { console.error(`Task ${result.index} failed: ${result.error.code} - ${result.error.message}`); } } } catch (error) { console.error('Batch request failed:', error.response?.data || error.message); } ``` ```python Python (requests) theme={null} import requests api_key = 'YOUR_API_KEY' url = 'https://api.cloro.dev/v1/async/task/batch' tasks = [ { "taskType": "CHATGPT", "priority": 5, "idempotencyKey": "batch-chatgpt-001", "webhook": {"url": "https://your-app.com/webhook-handler"}, "payload": {"prompt": "What do you know about Acme Corp?", "country": "US"}, }, { "taskType": "PERPLEXITY", "priority": 3, "idempotencyKey": "batch-perplexity-001", "payload": {"prompt": "Latest news about Acme Corp", "country": "US"}, }, { "taskType": "GEMINI", "payload": {"prompt": "Summarize Acme Corp recent announcements"}, }, ] response = requests.post( url, json=tasks, headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", }, ) data = response.json() summary = data["summary"] print(f"Batch complete: {summary['succeeded']} succeeded, {summary['failed']} failed") for result in data["results"]: if result["success"]: task = result["task"] print(f"Task {result['index']}: {task['id']} ({task['taskType']})") else: error = result["error"] print(f"Task {result['index']} failed: {error['code']} - {error['message']}") ``` ### Response with partial success ```json Response theme={null} { "success": true, "summary": { "total": 3, "succeeded": 2, "failed": 1 }, "results": [ { "success": true, "index": 0, "task": { "id": "b27a21e1-7c39-4aa2-a347-23e828c426f9", "taskType": "CHATGPT", "status": "QUEUED", "priority": 5, "createdAt": "2026-04-09T15:00:00.000Z", "latencyMs": null, "idempotencyKey": "batch-chatgpt-001" }, "credits": { "creditsToCharge": 10, "creditsCharged": null } }, { "success": true, "index": 1, "task": { "id": "c38b32f2-8d40-5bb3-b458-34f939d537e0", "taskType": "PERPLEXITY", "status": "QUEUED", "priority": 3, "createdAt": "2026-04-09T15:00:00.000Z", "latencyMs": null, "idempotencyKey": "batch-perplexity-001" }, "credits": { "creditsToCharge": 5, "creditsCharged": null } }, { "success": false, "index": 2, "error": { "code": "INSUFFICIENT_CREDITS", "message": "Not enough credits remaining", "timestamp": "2026-04-09T15:00:00.000Z" } } ] } ``` ## Use cases ### Bulk monitoring across providers Send the same prompt to several providers to compare responses: ```javascript theme={null} const providers = ['CHATGPT', 'PERPLEXITY', 'GEMINI', 'COPILOT']; const prompt = 'What do you know about Acme Corp?'; const tasks = providers.map((taskType, i) => ({ taskType, idempotencyKey: `compare-${Date.now()}-${i}`, webhook: { url: 'https://your-app.com/webhook-handler' }, payload: { prompt, country: 'US' }, })); const response = await axios.post('https://api.cloro.dev/v1/async/task/batch', tasks, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, }); ``` ### Scheduled batch jobs Process a recurring list of queries in one request: ```python theme={null} from datetime import date queries = [ "What is Acme Corp's market position?", "Who are Acme Corp's main competitors?", "What are Acme Corp's latest product launches?", ] tasks = [ { "taskType": "CHATGPT", "priority": 3, "idempotencyKey": f"daily-batch-{date.today()}-{i}", "webhook": {"url": "https://your-app.com/webhook-handler"}, "payload": {"prompt": query, "country": "US"}, } for i, query in enumerate(queries) ] response = requests.post( "https://api.cloro.dev/v1/async/task/batch", json=tasks, headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, ) ``` ### Handling partial failures Check results and retry only the failed tasks: ```javascript theme={null} const { summary, results } = response.data; if (summary.failed > 0) { const failedTasks = results .filter(r => !r.success) .map(r => ({ ...originalTasks[r.index], idempotencyKey: `${originalTasks[r.index].idempotencyKey}-retry`, })); // Retry failed tasks (with new idempotency keys) const retryResponse = await axios.post(url, failedTasks, { headers }); } ``` # Google Gemini citation pills schema Source: https://cloro.dev/docs/api-reference/endpoint/gemini/citation-pills Inline citation pills returned by the Gemini endpoint, with each cited source carried as a self-contained entry alongside the visible pill chip it came from. This section documents the **citationPills** data returned by the [Gemini endpoint](/docs/api-reference/endpoint/monitor-gemini). Gemini renders inline citation chips (icon-only chain-link glyphs, and "section-summary" chips that group multiple sources behind a single visible pill) next to its answer text. The `result.citationPills` array exposes those chips denormalized, as part of the Gemini response so no separate API call is needed: each entry is one **(pill, source)** pair carrying a per-source `label` (the source's own page title), a `citationPillId` that groups entries from the same chip, and the per-source `url`/`domain`/`description`/`position`. For use cases, pricing context, and copy-paste examples, see the [Gemini sources API](https://cloro.dev/gemini/sources/) page on the product site. When a chip cites N sources (including section-summary chips), the array contains N entries sharing the same `citationPillId` but carrying different per-source `label`, `url`, and `domain`. Group by `citationPillId` to recover the pill-level structure. The field is omitted from `result` when the answer has no pills. ## Example request ```json theme={null} { "prompt": "what is quantum computing", "country": "US" } ``` ## Citation pill structure | Field | Type | Description | | ---------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `label` | string | Per-source title from the sources rail (e.g. `"Wikipedia - Quantum Computing"`). Always present; may be an empty string when the rail has no title for this source — read `domain` / `url` for source identity in that case. | | `citationPillId` | integer | 1-based ordinal shared by all entries from the same chip. | | `url` | string | Direct URL of the cited source. | | `domain` | string | Host extracted from `url`, for grouping and display. | | `description` | string | Source snippet from the sources rail when Gemini ships one. Omitted when absent. | | `position` | integer | 1-based position of this source in the sibling [`result.sources`](/docs/api-reference/endpoint/gemini/sources) array. | ## Response example ```json theme={null} { "success": true, "result": { "text": "Quantum computing is a multidisciplinary field...", "sources": [ { "position": 1, "url": "https://en.wikipedia.org/wiki/Quantum_computing", "label": "Wikipedia - Quantum Computing", "description": "Quantum computing is a multidisciplinary field..." }, { "position": 2, "url": "https://www.ibm.com/quantum", "label": "IBM Quantum", "description": "IBM's quantum computing program" }, { "position": 3, "url": "https://research.google.com/quantum", "label": "Google Quantum AI", "description": "Google's quantum research" } ], "citationPills": [ { "label": "Wikipedia - Quantum Computing", "citationPillId": 1, "url": "https://en.wikipedia.org/wiki/Quantum_computing", "domain": "en.wikipedia.org", "description": "Quantum computing is a multidisciplinary field...", "position": 1 }, { "label": "Google Quantum AI", "citationPillId": 1, "url": "https://research.google.com/quantum", "domain": "research.google.com", "description": "Google's quantum research", "position": 3 } ] } } ``` # Google Gemini sources schema Source: https://cloro.dev/docs/api-reference/endpoint/gemini/sources Schema for source citations returned by the Google Gemini endpoint, including URLs, titles, snippets, and contextual descriptions for each reference. This section documents the **sources** data returned by the [Gemini endpoint](/docs/api-reference/endpoint/monitor-gemini): the citations behind the answer, part of the Gemini response so no separate API call is needed. They follow the [common sources structure](/docs/guides/making-requests/sync#sources-array-structure) (`position`, `url`, `label`, `description`). For use cases, pricing context, and copy-paste examples, see the [Gemini sources API](https://cloro.dev/gemini/sources/) page on the product site. Sources in Gemini Some prompts don't trigger Gemini's source-citing mechanism. An empty `sources` array is normal, not an error. ## Example request ```json theme={null} { "prompt": "Explain quantum entanglement", "country": "US" } ``` ## Response example ```json theme={null} { "success": true, "result": { "text": "Quantum entanglement is a physical phenomenon...", "sources": [ { "position": 1, "label": "Wikipedia", "url": "https://en.wikipedia.org/wiki/Quantum_entanglement", "description": "Quantum entanglement is a physical phenomenon that occurs..." } ] } } ``` # Get async status Source: https://cloro.dev/docs/api-reference/endpoint/get-async-status api-reference/openapi.json GET /v1/async/status Get organization-wide async queue metrics including queued and processing task counts, and concurrency usage. Reports the current state of your organization's async task queue: how many tasks are waiting, and how many concurrency slots you are using against your plan limit. Between those two numbers you can tell whether a delay comes from queue volume or from hitting the concurrency ceiling, and decide whether to throttle submission or move to a higher plan. ## Response fields | Field | Type | Description | | ------------------------------ | ------- | -------------------------------------------------------------------------------------------------------------- | | `queuedTasks` | integer | Number of tasks currently queued for your organization (status `QUEUED`) | | `processingTasks` | integer | Number of tasks currently being processed for your organization (status `PROCESSING`) | | `priorityBreakdown` | array | Queued task counts per priority level, ordered by priority descending. Only includes levels with queued tasks. | | `priorityBreakdown[].priority` | integer | The priority level (1-10) | | `priorityBreakdown[].count` | integer | Number of queued tasks at this priority level | | `concurrency.used` | integer | Number of concurrent slots currently in use | | `concurrency.max` | integer | Maximum allowed concurrent tasks for your organization (based on your plan) | The `concurrency` object may be `null` if concurrency information cannot be retrieved at the time of the request. This is rare but can occur during system maintenance. Looking for your credit balance? Call [`GET /v1/credits`](/docs/api-reference/endpoint/get-credits) — it returns your remaining balance and cycle reset date without the queue aggregations this endpoint runs. ## Example usage ### Check current queue status ```bash cURL theme={null} curl -X GET "https://api.cloro.dev/v1/async/status" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "queuedTasks": 3, "processingTasks": 2, "priorityBreakdown": [ { "priority": 10, "count": 1 }, { "priority": 5, "count": 1 }, { "priority": 1, "count": 1 } ], "concurrency": { "used": 2, "max": 5 } } ``` ### Monitor queue status programmatically ```javascript Node.js theme={null} import axios from 'axios'; const API_KEY = process.env.API_KEY; const STATUS_URL = 'https://api.cloro.dev/v1/async/status'; async function checkQueueStatus() { try { const response = await axios.get(STATUS_URL, { headers: { 'Authorization': `Bearer ${API_KEY}` } }); const { queuedTasks, processingTasks, priorityBreakdown, concurrency } = response.data; console.log(`Queue status:`); console.log(` Queued: ${queuedTasks} tasks`); console.log(` Processing: ${processingTasks} tasks`); if (priorityBreakdown.length > 0) { console.log(` Priority breakdown:`); for (const { priority, count } of priorityBreakdown) { console.log(` Priority ${priority}: ${count} tasks`); } } if (concurrency) { console.log(` Concurrency: ${concurrency.used}/${concurrency.max} slots used`); console.log(` Available: ${concurrency.max - concurrency.used} slots`); } // Alert if queue is getting large if (queuedTasks > 100) { console.warn('Warning: Queue has over 100 tasks waiting'); } // Alert if approaching concurrency limit if (concurrency && concurrency.used / concurrency.max > 0.8) { console.warn('Warning: Using over 80% of concurrency limit'); } return response.data; } catch (error) { console.error('Error checking queue status:', error.message); throw error; } } // Check status every 30 seconds setInterval(checkQueueStatus, 30000); ``` ```python Python theme={null} import requests import time API_KEY = 'YOUR_API_KEY' STATUS_URL = 'https://api.cloro.dev/v1/async/status' def check_queue_status(): try: response = requests.get( STATUS_URL, headers={'Authorization': f'Bearer {API_KEY}'} ) response.raise_for_status() data = response.json() queued_tasks = data['queuedTasks'] processing_tasks = data['processingTasks'] priority_breakdown = data.get('priorityBreakdown', []) concurrency = data.get('concurrency') print(f'Queue status:') print(f' Queued: {queued_tasks} tasks') print(f' Processing: {processing_tasks} tasks') if priority_breakdown: print(f' Priority breakdown:') for entry in priority_breakdown: print(f' Priority {entry["priority"]}: {entry["count"]} tasks') if concurrency: used = concurrency['used'] max_concurrent = concurrency['max'] available = max_concurrent - used print(f' Concurrency: {used}/{max_concurrent} slots used') print(f' Available: {available} slots') # Alert if approaching concurrency limit if used / max_concurrent > 0.8: print('Warning: Using over 80% of concurrency limit') # Alert if queue is getting large if queued_tasks > 100: print('Warning: Queue has over 100 tasks waiting') return data except requests.exceptions.RequestException as e: print(f'Error checking queue status: {e}') raise # Check status every 30 seconds while True: check_queue_status() time.sleep(30) ``` ## Use cases ### Capacity planning Watch concurrency usage to decide when to upgrade your plan: ```javascript theme={null} const stats = await checkQueueStatus(); // If consistently at or near max concurrency, consider upgrading if (stats.concurrency.used >= stats.concurrency.max * 0.9) { console.log('Consider upgrading to a higher plan for better throughput'); } ``` ### Queue health monitoring Track queue size to catch bottlenecks and high-volume periods: ```python theme={null} data = check_queue_status() # Alert if queue is backing up if data['queuedTasks'] > 1000: send_alert('High queue volume - consider optimizing task submission') ``` ### Throttling task submission Adjust your submission rate to the current queue depth: ```javascript theme={null} const status = await checkQueueStatus(); // Only submit more tasks if queue is manageable if (status.queuedTasks < 500) { await submitNextBatch(); } else { console.log('Queue is full, waiting before submitting more tasks'); await delay(60000); // Wait 1 minute } ``` # Get credit balance Source: https://cloro.dev/docs/api-reference/endpoint/get-credits api-reference/openapi.json GET /v1/credits Read your organization's current credit balance and billing cycle programmatically, without spending a billable /v1/monitor/* request to check a header. Read your organization's current credit balance and billing cycle without opening the dashboard. On an async-only workload this saves you a billable sync `/v1/monitor/*` request made only to read the `X-Credits-Remaining` header. Poll it to alert your team before you run out, and to pause task submission when the balance is too low to cover the next batch. ## Response fields | Field | Type | Description | | --------------- | --------------- | ---------------------------------------------------------------------------------------------------------------- | | `remaining` | integer | Credits currently remaining for your organization | | `perCycle` | integer \| null | Credits granted per billing cycle. `null` for free-tier organizations with no active subscription | | `cycleResetsAt` | string \| null | ISO 8601 timestamp for when the current billing cycle ends and credits reset. `null` for free-tier organizations | ## Example usage ### Read your current balance ```bash cURL theme={null} curl -X GET "https://api.cloro.dev/v1/credits" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "remaining": 48210, "perCycle": 1562500, "cycleResetsAt": "2026-08-10T17:35:27.000Z" } ``` ### Low-balance alerts and a submission breaker ```javascript Node.js theme={null} import axios from 'axios'; const API_KEY = process.env.API_KEY; const CREDITS_URL = 'https://api.cloro.dev/v1/credits'; const COST_PER_BATCH = 5000; async function getCredits() { const { data } = await axios.get(CREDITS_URL, { headers: { Authorization: `Bearer ${API_KEY}` }, }); return data; } const { remaining, cycleResetsAt } = await getCredits(); if (remaining < 10_000) { const resetInfo = cycleResetsAt ? `resets ${cycleResetsAt}` : 'no active billing cycle'; console.warn(`Low credit balance: ${remaining} remaining (${resetInfo})`); } if (remaining < COST_PER_BATCH) { console.log('Insufficient credits — pausing submission until the cycle resets or you top up'); } else { await submitNextBatch(); } ``` ```python Python theme={null} import os import requests API_KEY = os.environ['API_KEY'] CREDITS_URL = 'https://api.cloro.dev/v1/credits' COST_PER_BATCH = 5000 def get_credits(): response = requests.get( CREDITS_URL, headers={'Authorization': f'Bearer {API_KEY}'} ) response.raise_for_status() return response.json() credits = get_credits() remaining = credits['remaining'] cycle_resets_at = credits['cycleResetsAt'] if remaining < 10_000: reset_info = f'resets {cycle_resets_at}' if cycle_resets_at else 'no active billing cycle' print(f'Low credit balance: {remaining} remaining ({reset_info})') if remaining < COST_PER_BATCH: print('Insufficient credits — pausing submission until the cycle resets or you top up') else: submit_next_batch() ``` Every task is checked against your live balance when it's charged, so this read is a guardrail rather than a ledger. `remaining` counts completed charges only — it does not net out work you have already queued, so subtract your own outstanding `creditsToCharge` before sizing the next batch. To attribute exact cost per task, use the `creditsCharged` field on the [task status](/docs/api-reference/endpoint/get-task-status) response instead. # Get task status Source: https://cloro.dev/docs/api-reference/endpoint/get-task-status api-reference/openapi.json GET /v1/async/task/{taskId} Poll the status and result of an asynchronous task by ID. Returns the task state and, once complete, the full structured result payload. Poll this endpoint to check the status of an asynchronous task. While the task is `QUEUED` (received, waiting) or `PROCESSING` (actively running), the response carries only the current status. Once it is `COMPLETED` (finished successfully) or `FAILED`, the response also includes a `response` object with the full result or error details. ## Task retention policy `COMPLETED` and `FAILED` tasks are retained for **24 hours** after completion, then the task record and its response data are permanently deleted. HTML URLs included in responses expire **24 hours** after generation, regardless of the task's retention status. ## Example usage ### Check a pending task ```bash cURL theme={null} curl -X GET "https://api.cloro.dev/v1/async/task/b27a21e1-7c39-4aa2-a347-23e828c426f9" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response (QUEUED) theme={null} { "task": { "id": "b27a21e1-7c39-4aa2-a347-23e828c426f9", "taskType": "CHATGPT", "status": "QUEUED", "priority": 1, "createdAt": "2025-11-10T15:00:00.000Z", "latencyMs": null, "idempotencyKey": "your-unique-key" }, "credits": { "creditsToCharge": 10, "creditsCharged": 0 } } ``` ### Fetch a completed task ```bash cURL theme={null} curl -X GET "https://api.cloro.dev/v1/async/task/b27a21e1-7c39-4aa2-a347-23e828c426f9" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response (COMPLETED) theme={null} { "task": { "id": "b27a21e1-7c39-4aa2-a347-23e828c426f9", "taskType": "CHATGPT", "status": "COMPLETED", "priority": 1, "createdAt": "2025-11-10T15:00:00.000Z", "latencyMs": 3420, "idempotencyKey": "your-unique-key" }, "credits": { "creditsToCharge": 10, "creditsCharged": 10 }, "response": { "model": "gpt-5-3-mini", "html": "...", "text": "...", "sources": [], "shoppingCards": [], "entities": [], "markdown": "...", "searchQueries": [] } } ``` # Google News articles schema Source: https://cloro.dev/docs/api-reference/endpoint/google-news/news-articles Schema for structured news articles returned by the Google News endpoint, including titles, links, snippets, sources, publication dates, and thumbnails. This section documents the **news articles** data returned by the [Google News endpoint](/docs/api-reference/endpoint/monitor-google-news). News articles are part of the Google News response, so no separate API call is needed. For use cases, pricing context, and copy-paste examples, see the [Google News API](https://cloro.dev/google-news/) page on the product site. News articles in Google News ## Example request ```json theme={null} { "query": "climate change", "country": "US" } ``` ## Article fields | Field | Type | Description | | ----------- | ------ | -------------------------------------- | | `position` | number | Position in news results (1-indexed) | | `title` | string | Title of the news article | | `link` | string | URL of the news article | | `snippet` | string | Text snippet describing the article | | `source` | string | News source/publisher name | | `date` | string | Publication date (e.g., "2 hours ago") | | `thumbnail` | string | Thumbnail image URL (when available) | ## Response example ```json theme={null} { "success": true, "result": { "newsResults": [ { "position": 1, "title": "Breaking climate news", "link": "https://example.com/article", "snippet": "Scientists report new findings on...", "source": "Example News", "date": "2 hours ago", "thumbnail": "https://example.com/thumb.jpg" } ] } } ``` # Google AI Overview schema Source: https://cloro.dev/docs/api-reference/endpoint/google/ai-overview AI Overview data returned by the Google Search endpoint, including text, markdown, sources, citation pills, related links, videos, and sponsored ads. AI Overview is Google's AI-generated summary at the top of certain search results, with a text summary, cited sources, optional videos, and occasionally sponsored ads. It is returned by the [Google Search endpoint](/docs/api-reference/endpoint/monitor-google) when you set `include.aioverview` — no separate API call. For use cases, pricing context, and copy-paste examples, see the [Google AI Overview API](https://cloro.dev/ai-overview/) page on the product site. AI Overview in Google Search AI Overview availability varies by country. Some regions are not supported and return an `UnsupportedInputError` when `include.aioverview` is set — this is a region-level error, not a query failure. Other supported regions may return `aioverview: null` when Google does not show an AI Overview for a specific query. In both cases, all other Google Search result data is returned normally. Use the [`/v1/countries`](/docs/api-reference/endpoint/countries) endpoint with `model=aioverview` to check current regional availability before sending requests. ## Example request ```json theme={null} { "query": "best laptops for programming", "country": "US", "include": { "aioverview": { "markdown": true } } } ``` ## AI Overview structure | Field | Type | Description | | --------------------------------- | ------ | -------------------------------------------------------------------------------- | | `result.aioverview` | object | Google AI Overview data (if requested) | | `result.aioverview.text` | string | AI Overview text content | | `result.aioverview.markdown` | string | AI Overview in markdown format (if requested) | | `result.aioverview.sources` | array | Sources referenced in AI Overview | | `result.aioverview.citationPills` | array | Inline citation pills, denormalized per cited source (when present) | | `result.aioverview.relatedLinks` | array | Pill "related links" — grouped-but-uncited URLs, not in `sources` (when present) | | `result.aioverview.videos` | array | Videos included in AI Overview | | `result.aioverview.ads` | array | Sponsored ads injected in AI Overview | ## Sources `result.aioverview.sources` follows the [common sources structure](/docs/guides/making-requests/sync#sources-array-structure) (`position`, `url`, `label`, and `description`). Sources in AI Overview ## Citation pills Inline citation pills (e.g. `[Chase Bank +3]`) Google renders next to the AI Overview text are exposed denormalized: each entry is one **(pill, source)** pair carrying a per-source `label` (the source's own page title). When a pill cites N sources, the array contains N entries sharing the same `citationPillId` but carrying different per-source `label`, `url`, and `domain`. Group by `citationPillId` to recover the pill-level structure. The field is omitted when the answer carries no pills. | Field | Type | Description | | ---------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `label` | string | Per-source title from the sources rail (e.g. `"Best laptops for developers"`). Always present; may be an empty string when the rail has no title for this source — read `domain` / `url` for source identity in that case. | | `citationPillId` | integer | Stable identifier shared by all entries from the same visible chip. 1-based ordinal assigned in document order. | | `url` | string | Direct URL of the cited source. | | `domain` | string | Host extracted from `url`, for grouping and display. | | `description` | string | Source snippet from the sources rail when Google ships one. Omitted when absent. | | `position` | integer | 1-based position of this source in the sibling `result.aioverview.sources` array. | ## Related links A citation chip can expose a **"View related links"** flyout — URLs Google groups under the chip that are **not** part of the [sources](#sources) rail (for example a Google Shopping comparison link). They surface separately in `result.aioverview.relatedLinks` so they never inflate `sources` or `citationPills`. Each entry shares the `citationPillId` of its chip; unlike a citation pill it has **no `position`**, since it is deliberately absent from the sources array. Related links also render in `markdown` as inline `[title](url)` links, so `markdown` can contain more URLs than `sources` / `citationPills` — the extras are always the related links in this array. The field is omitted when a chip carries no related links. | Field | Type | Description | | ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `label` | string | The related page's own title (e.g. `"Compare developer laptops"`). May be an empty string when Google ships none — read `domain` / `url` for identity. | | `citationPillId` | integer | Matches the `citationPillId` of the chip's citation-pill entries, so related links can be grouped with their pill. | | `url` | string | Direct URL of the related link. | | `domain` | string | Host extracted from `url`, for grouping and display. | | `description` | string | Snippet Google ships for the related link. Omitted when absent. | ## Videos Video content included in AI Overview. Videos appear in the order Google embeds them in the AI Overview answer; array index `0` is the first card shown. Videos in AI Overview | Field | Type | Description | | ----------- | ------ | ------------------------------ | | `url` | string | Direct URL to the video | | `title` | string | Video title | | `thumbnail` | string | Thumbnail image URL | | `source` | string | Channel or source name | | `platform` | string | Video platform (e.g., YouTube) | | `date` | string | Upload date | | `duration` | string | Video duration | Only `url` is guaranteed. Every other field is best-effort: Google does not attach every piece of metadata to every video card, and `thumbnail` and `duration` in particular are only available when Google renders the rich carousel preview (roughly 60% and 15% of videos in practice). Check for field presence before reading. ## Ads Sponsored ads injected by Google inside the AI Overview. Each entry in `ads[]` is one of two sub-types — text/lead-gen or shopping/product — discriminated by the `type` field. Sub-type-exclusive fields are only present when they apply to that ad. Ads in AI Overview | Field | Type | Always present | Description | | ------------- | ------ | -------------------------------------------- | ---------------------------------------------------------------------- | | `position` | number | Yes | Position of ad (1-indexed) | | `title` | string | Yes | Ad title | | `url` | string | Yes | Ad destination URL | | `type` | string | Yes | Sub-type discriminator: `"TEXT"` or `"SHOPPING"` | | `domain` | string | When `type` is `TEXT` | Domain name of the advertiser | | `description` | string | When `type` is `TEXT` | Ad description text | | `price` | object | When `type` is `SHOPPING` | Product price | | `old_price` | object | When `type` is `SHOPPING`, with sale pricing | Original price before discount | | `store` | string | When `type` is `SHOPPING` | Retailer name | | `image` | string | When extracted | Ad image URL (product photo for shopping ads, hero image for text ads) | ### Ad price object `price` and `old_price` carry both a parsed and a raw representation: | Field | Type | Description | | ---------- | ------ | ------------------------------------------------------------------------------------ | | `value` | number | Numeric price. Present when the visible string parses unambiguously into a number. | | `currency` | string | Currency symbol (e.g. `$`, `£`, `€`). Present when a recognized symbol is detected. | | `raw` | string | Visible price text verbatim (e.g. `"$1,499"`, `"$0 down with 24 monthly payments"`). | `raw` is always present when `price` is emitted. Installment / down-payment labels parse the leading `"$0"` as `value: 0` — use `raw` to disambiguate. ## Response example ```json theme={null} { "success": true, "result": { "aioverview": { "text": "For programming, look for laptops with at least 16GB of RAM, a fast SSD, and a comfortable keyboard...", "markdown": "For programming, look for laptops with at least **16GB of RAM**, a fast SSD, and a comfortable keyboard...[Best laptops for developers](https://example.com/best-dev-laptops)[Compare developer laptops](https://www.google.com/search?q=developer+laptops&ibp=oshop)", "sources": [ { "position": 1, "url": "https://example.com/best-dev-laptops", "label": "Best laptops for developers", "description": "Guide to laptops for software development." } ], "citationPills": [ { "label": "Best laptops for developers", "citationPillId": 1, "url": "https://example.com/best-dev-laptops", "domain": "example.com", "description": "Guide to development laptops", "position": 1 } ], "relatedLinks": [ { "label": "Compare developer laptops", "citationPillId": 1, "url": "https://www.google.com/search?q=developer+laptops&ibp=oshop", "domain": "google.com" } ], "videos": [ { "url": "https://www.youtube.com/watch?v=example", "title": "Top 5 Laptops for Programmers", "thumbnail": "https://i.ytimg.com/vi/example/hqdefault.jpg", "source": "Tech Channel", "platform": "YouTube", "date": "2026-02-10", "duration": "12:34" } ], "ads": [ { "position": 1, "type": "TEXT", "title": "Dell XPS 15 — Developer Edition", "url": "https://www.dell.com/xps-15-developer", "domain": "dell.com", "description": "Pre-configured for Linux development with 32GB RAM.", "image": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ..." }, { "position": 2, "type": "SHOPPING", "title": "ThinkPad X1 Carbon Gen 12", "url": "https://www.bestbuy.com/site/thinkpad-x1-carbon-gen-12/abc123", "price": { "value": 1499, "currency": "$", "raw": "$1,499" }, "old_price": { "value": 1799, "currency": "$", "raw": "$1,799" }, "store": "Best Buy", "image": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR..." } ] } } } ``` # Google Knowledge Graph schema Source: https://cloro.dev/docs/api-reference/endpoint/google/knowledge-graph Schema for the Knowledge Graph panel returned by the Google Search endpoint: entity details, attributes, ratings, profiles, and panel-specific fields. Google's Knowledge Graph panel surfaces structured information about a queried entity — a person, place, organization, film, product, or other well-known subject. When a panel appears, the [Google Search endpoint](/docs/api-reference/endpoint/monitor-google) includes a `knowledgeGraph` object in the same response; when none appears the field is omitted, so treat it as optional rather than expecting `null`. For use cases, pricing context, and copy-paste examples, see the [Google Knowledge Graph API](https://cloro.dev/serp-api/knowledge-graph/) page on the product site. Knowledge Graph panel in Google Search Layout and sub-fields vary by entity type — an artist panel may carry `listenOn` and `socialPosts`, a restaurant `contact` and `webRatings`. Every sub-field is optional and appears only when Google includes it. ## Top-level fields | Field | Type | Description | | --------------------- | ------- | --------------------------------------------------------------------------------------------------- | | `title` | string | Entity name as shown in the panel header | | `type` | string | Entity category label (e.g. `"American rapper"`, `"French museum"`). Free-form string — not an enum | | `kgmid` | string | Google Knowledge Graph ID for the entity | | `description` | string | Short entity description (sourced from Wikipedia or Google's knowledge base) | | `imageUrl` | string | Entity image URL as served by Google | | `website` | string | Official website URL | | `menu` | string | Menu URL — present for restaurants and food businesses | | `trailer` | string | Trailer URL — present for films and TV shows | | `source` | object | Attribution source for the description (see [`source`](#source)) | | `attributes` | array | Factual key-value pairs (see [`attributes`](#attributes)) | | `profiles` | array | Social media and external profile links (see [`profiles`](#profiles)) | | `peopleAlsoSearchFor` | array | Related entities users also search for (see [`peopleAlsoSearchFor`](#peoplealsosearchfor)) | | `thingsToKnow` | array | Topic clusters from the "Things to know" section (see [`thingsToKnow`](#thingstoknow)) | | `ratings` | array | Review platform ratings, e.g. Rotten Tomatoes, IMDb (see [`ratings`](#ratings)) | | `streamingOptions` | array | Streaming links for films and shows (see [`streamingOptions`](#streamingoptions)) | | `webRatings` | array | Local business ratings from Google, Tripadvisor, Yelp, etc. (see [`webRatings`](#webratings)) | | `statsCard` | object | Statistics card for sports teams and organizations (see [`statsCard`](#statscard)) | | `socialPosts` | array | Recent posts from the entity's official social accounts (see [`socialPosts`](#socialposts)) | | `notableAlumni` | array | Notable alumni — present for educational institutions (see [`notableAlumni`](#notablealumni)) | | `contact` | array | Contact and ordering links for local businesses (see [`contact`](#contact)) | | `weather` | object | Current weather — present for geographic entities (see [`weather`](#weather)) | | `rating` | object | Aggregate star rating for local businesses (see [`rating`](#rating)) | | `admission` | array | Admission options for museums, parks, and ticketed venues (see [`admission`](#admission)) | | `artworks` | array | Notable artworks for artists and art institutions (see [`artworks`](#artworks)) | | `has3dModel` | boolean | `true` when Google surfaces a 3D model viewer for the entity | | `hotelOptions` | array | Hotel booking options — present for hotel entities (see [`hotelOptions`](#hoteloptions)) | | `trendingProducts` | array | Trending products for retail brands (see [`trendingProducts`](#trendingproducts)) | | `merchantVideos` | array | Brand or merchant video content (see [`merchantVideos`](#merchantvideos)) | | `listenOn` | array | Music streaming links for musicians and musical acts (see [`listenOn`](#listenon)) | ## Sub-type schemas ### `source` Attribution source for the entity description. | Field | Type | Description | | ------ | ------ | ----------------------------------------------- | | `name` | string | Display name of the source (e.g. `"Wikipedia"`) | | `link` | string | URL of the source page | ### `attributes` Factual key-value pairs extracted from the panel (born date, genre, headquarters, founding year, etc.). Keys are free-form strings as rendered by Google. | Field | Type | Description | | ------- | ------ | ------------------------------------------------------------ | | `key` | string | Attribute label (e.g. `"Born"`, `"Genre"`, `"Headquarters"`) | | `value` | string | Attribute value | ### `profiles` Social media and external profile links. | Field | Type | Description | | ------ | ------ | ---------------------------------------------------- | | `name` | string | Platform or profile name (e.g. `"Instagram"`, `"X"`) | | `link` | string | Profile URL | ### `peopleAlsoSearchFor` Related entities that users also search for. | Field | Type | Description | | ------ | ------ | ---------------------------------------- | | `name` | string | Related entity name | | `link` | string | Google search URL for the related entity | ### `thingsToKnow` Topic clusters from the "Things to know" section of the panel. | Field | Type | Description | | ---------- | ------ | ------------------------------------------------- | | `label` | string | Topic cluster label (e.g. `"Albums"`, `"Awards"`) | | `subtitle` | string | Supporting text for the topic | ### `ratings` Review platform ratings for films, TV shows, and similar entities. | Field | Type | Description | | ---------- | ------ | --------------------------------------------------------- | | `platform` | string | Review platform name (e.g. `"Rotten Tomatoes"`, `"IMDb"`) | | `rating` | string | Rating value as displayed (e.g. `"88%"`, `"7.5/10"`) | | `link` | string | URL to the review page | ### `streamingOptions` Streaming platform links for films and TV shows. | Field | Type | Description | | ---------- | ------ | --------------------------------------------------- | | `platform` | string | Streaming platform name (e.g. `"Netflix"`, `"Max"`) | | `link` | string | Link to the content on the platform | ### `webRatings` Local business ratings from review platforms. Different from `ratings` — these appear on local business panels (restaurants, hotels, attractions) rather than film/show panels. | Field | Type | Description | | ---------- | ------ | ----------------------------------------------------------------- | | `platform` | string | Rating platform name (e.g. `"Google"`, `"Tripadvisor"`, `"Yelp"`) | | `rating` | string | Rating value as displayed | | `link` | string | URL to the platform's review page | | `votes` | string | Number of reviews (omitted when not available) | ### `statsCard` Statistics card for sports teams, athletes, and organizations. | Field | Type | Description | | --------- | ------ | --------------------------------------------------------- | | `context` | string | Context label for the stats (e.g. `"2023-24 NBA season"`) | | `stats` | array | Individual stat items (`label` string, `value` string) | ### `socialPosts` Recent posts from the entity's official social media accounts. | Field | Type | Description | | ---------- | ------ | ------------------------------------------------------ | | `platform` | string | Social platform name (e.g. `"X"`, `"Instagram"`) | | `text` | string | Post text content | | `link` | string | URL to the post | | `date` | string | Post date as displayed by Google (e.g. `"2 days ago"`) | ### `notableAlumni` Notable alumni — present for universities and educational institutions. | Field | Type | Description | | ------ | ------ | --------------------------------- | | `name` | string | Alumni name | | `link` | string | Google search URL for this person | ### `contact` Contact and ordering links for local businesses. | Field | Type | Description | | ------- | ------ | ------------------------------------------------------- | | `label` | string | Link label (e.g. `"Order online"`, `"Reserve a table"`) | | `url` | string | Link URL | ### `weather` Current weather — present for geographic entities (cities, regions, countries). | Field | Type | Description | | ------------- | ------ | ----------------------------------------------------- | | `temperature` | string | Temperature as displayed (e.g. `"72°F"`) | | `condition` | string | Weather condition (e.g. `"Sunny"`, `"Partly cloudy"`) | ### `rating` Aggregate star rating for local businesses. | Field | Type | Description | | -------- | ------ | ---------------------------------- | | `rating` | string | Star rating value (e.g. `"4.5"`) | | `count` | string | Number of reviews (e.g. `"2,847"`) | ### `admission` Admission purchase options for museums, parks, and ticketed venues. | Field | Type | Description | | ---------- | ------ | ------------------------------------------------------------------------------ | | `provider` | string | Ticket provider name (e.g. `"Official website"`, `"GetYourGuide"`) | | `price` | string | Admission price as displayed | | `link` | string | URL to purchase tickets | | `badge` | string | Optional badge text (e.g. `"Free"`, `"Best price"`) — omitted when not present | ### `artworks` Notable artworks — present for artists and art institutions. | Field | Type | Description | | -------- | ------ | ------------------------------------------------- | | `title` | string | Artwork title | | `link` | string | Link to more information about the artwork | | `author` | string | Author or creator name — omitted when not present | ### `hotelOptions` Hotel booking options — present for hotel entities. | Field | Type | Description | | ---------- | ------ | ---------------------------------------------------------------------- | | `provider` | string | Booking provider name (e.g. `"Booking.com"`, `"Hotels.com"`) | | `price` | string | Price as displayed (e.g. `"$189/night"`) | | `link` | string | URL to book on this provider | | `note` | string | Optional note (e.g. `"Includes breakfast"`) — omitted when not present | ### `trendingProducts` Trending products — present for retail brands. | Field | Type | Description | | ------- | ------ | ------------------- | | `title` | string | Product title | | `price` | string | Price as displayed | | `link` | string | Link to the product | ### `merchantVideos` Brand or merchant video content — present for retail businesses. | Field | Type | Description | | ---------- | ------ | ------------------------------------------------------------------------ | | `title` | string | Video title | | `link` | string | Video URL | | `platform` | string | Video platform (e.g. `"YouTube"`) — omitted when not available | | `duration` | string | Video duration as displayed (e.g. `"2:34"`) — omitted when not available | ### `listenOn` Music streaming platform links — present for musicians and musical acts. | Field | Type | Description | | ---------- | ------ | ----------------------------------------------------------- | | `platform` | string | Streaming platform name (e.g. `"Spotify"`, `"Apple Music"`) | | `link` | string | Link to the artist on the platform | ## Response example The following example shows a Knowledge Graph panel for a musician, with several panel-type-specific fields populated. ```json theme={null} { "success": true, "result": { "knowledgeGraph": { "title": "Eminem", "type": "American rapper", "kgmid": "/m/01vs_v8", "description": "Marshall Bruce Mathers III, known professionally as Eminem, is an American rapper, songwriter, and record producer.", "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:...", "website": "https://www.eminem.com", "source": { "name": "Wikipedia", "link": "https://en.wikipedia.org/wiki/Eminem" }, "attributes": [ { "key": "Born", "value": "October 17, 1972" }, { "key": "Genre", "value": "Hip hop" }, { "key": "Labels", "value": "Aftermath, Interscope, Shady" } ], "profiles": [ { "name": "Instagram", "link": "https://www.instagram.com/eminem/" }, { "name": "X", "link": "https://x.com/Eminem" } ], "peopleAlsoSearchFor": [ { "name": "Jay-Z", "link": "https://www.google.com/search?q=Jay-Z" }, { "name": "Dr. Dre", "link": "https://www.google.com/search?q=Dr.+Dre" } ], "thingsToKnow": [ { "label": "Albums", "subtitle": "The Slim Shady LP, The Marshall Mathers LP, Recovery, ..." }, { "label": "Awards", "subtitle": "15 Grammy Awards, Academy Award for Best Original Song" } ], "socialPosts": [ { "platform": "X", "text": "The Death of Slim Shady out now everywhere 🎤", "link": "https://x.com/Eminem/status/...", "date": "3 days ago" } ], "listenOn": [ { "platform": "Spotify", "link": "https://open.spotify.com/artist/7dGJo4pcD2V6oG8kP0tJRR" }, { "platform": "Apple Music", "link": "https://music.apple.com/us/artist/eminem/111051" } ] } } } ``` # Google Local Pack schema Source: https://cloro.dev/docs/api-reference/endpoint/google/local-pack Schema for Google's local pack (map-backed 3-pack) returned by the Google Search endpoint, including ratings, category, address, phone, and hours. Google's local pack (the "3-pack") is the map-backed block of local businesses shown for local-intent queries such as `best pizza in new york city` or `plumbers in houston`. The [Google Search endpoint](/docs/api-reference/endpoint/monitor-google) returns it as `result.localResults`, one entry per place — no separate API call. For use cases, pricing context, and copy-paste examples, see the [Local Pack API](https://cloro.dev/serp-api/local-pack/) page on the product site. Local pack on the Google SERP `localResults` is omitted from `result` when the module is absent — treat it as optional rather than expecting an empty array. The pack only exposes its visible places (typically 3). When Google renders a "More places" / "More businesses" control, the response also includes `result.localResultsMoreLink` — the absolute URL of Google's expanded [Local Finder](https://support.google.com/business/answer/7025874) for the query. The expanded list is not embedded in this response; follow that link to retrieve it. Local pack extraction is **desktop only**. Mobile SERPs render the pack with a different layout, and Google's separate hotels/travel pack is a distinct module — neither is surfaced in `localResults`. ## Place structure `position` and `title` are always present. Every other field is included only when Google renders it for that place, so field availability varies by query (restaurant packs tend to carry `price`; service packs tend to carry `yearsInBusiness`, `phone`, and `hours`). | Field | Type | Description | | ----------------- | ------ | -------------------------------------------------------------------------------------------------- | | `position` | number | Position within the pack (1-indexed) | | `title` | string | Business name as displayed on the SERP | | `placeId` | string | Google entity id for the place (e.g. `"/g/11bw4ws2mt"`) | | `rating` | number | Average star rating (0–5) | | `reviews` | string | Review count as displayed by Google (e.g. `"26K"`). Free-form string — not normalized to a number. | | `price` | string | Price band as displayed by Google (e.g. `"$10–20"`). Currency symbol matches the locale. | | `type` | string | Business category (e.g. `"Pizza"`, `"Plumber"`) | | `yearsInBusiness` | string | Years-in-business badge, when shown (typical on service-style packs) | | `address` | string | Street address or locality as displayed on the SERP | | `phone` | string | Phone number, when shown (locale-formatted) | | `hours` | string | Opening-hours text as displayed (e.g. `"Open · Closes 11 PM"`, `"Open 24 hours"`) | | `description` | string | Trailing snippet — a review quote or a service-options line | | `links` | object | Action links, when rendered. See [Links](#links) below. | ### Links Service-style packs (plumbers, salons, dentists, …) render each place with action buttons, which arrive in `links`: | Field | Type | Description | | ------------ | ------ | ----------------------------------- | | `website` | string | The business's own website URL | | `directions` | string | Absolute Google Maps directions URL | Each key is included only when Google rendered that button. Restaurant packs render places as plain buttons and carry no `links` object. ## Response examples ### Restaurant pack A response to `best pizza in new york city`: ```json theme={null} { "success": true, "result": { "localResults": [ { "position": 1, "title": "Joe's Pizza Broadway", "placeId": "/g/11bw4ws2mt", "rating": 4.4, "reviews": "26K", "price": "$10–20", "type": "Pizza", "address": "1435 Broadway", "description": "\"Fast service, great atmosphere, and truly scrumptious pizza.\"" }, { "position": 2, "title": "John's of Bleecker Street", "placeId": "/g/11b8zbnw8g", "rating": 4.6, "reviews": "8.4K", "price": "$10–20", "type": "Pizza", "address": "278 Bleecker St", "description": "\"Funky fun vibes, efficient and polite staff, DELICIOUS pizza.\"" } ], "localResultsMoreLink": "https://www.google.com/search?q=best+pizza+in+new+york+city&udm=1" } } ``` ### Service pack A response to `plumbers in houston`: ```json theme={null} { "success": true, "result": { "localResults": [ { "position": 1, "title": "Village Plumbing, Air & Electric", "placeId": "/g/1td7nq1l", "rating": 4.8, "reviews": "10K", "type": "Plumber", "yearsInBusiness": "80+ years in business", "address": "Houston, TX", "phone": "(281) 344-2270", "hours": "Open · Closes 8 PM", "description": "Online estimates · Onsite services not available", "links": { "website": "https://villageplumbing.com/", "directions": "https://www.google.com/maps/dir//Village+Plumbing,+Air+%26+Electric" } }, { "position": 2, "title": "Cooper Plumbing | Houston Plumber", "placeId": "/g/11h511y417", "rating": 4.9, "reviews": "550", "type": "Plumber", "yearsInBusiness": "10+ years in business", "address": "Houston, TX", "phone": "(832) 441-9683", "hours": "Open 24 hours", "description": "Onsite services not available", "links": { "website": "https://www.cooperplumbinghouston.com/", "directions": "https://www.google.com/maps/dir//Cooper+Plumbing" } } ], "localResultsMoreLink": "https://www.google.com/search?q=plumbers+in+houston&udm=1" } } ``` # Google organic search results schema Source: https://cloro.dev/docs/api-reference/endpoint/google/organic-results Schema for organic search results returned by the Google Search endpoint, including page title, URL, snippet, position, sitelinks, and rich enrichments. This section documents the **organic results** data returned by the [Google Search endpoint](/docs/api-reference/endpoint/monitor-google): the standard non-paid search results, each with title, link, snippet, and position metadata, part of the Google response so no separate API call is needed. For use cases, pricing context, and copy-paste examples, see the [Google organic results API](https://cloro.dev/serp-api/organic-results/) page on the product site. Organic results in Google Search ## Example request ```json theme={null} { "query": "best laptops for programming", "country": "US" } ``` ## Organic result structure | Field | Type | Description | | --------------- | ------ | --------------------------------------------------- | | `position` | number | Position in search results (1-indexed) | | `title` | string | Title of the search result | | `link` | string | URL of the search result | | `displayedLink` | string | Formatted URL as displayed in search results | | `snippet` | string | Search result snippet | | `page` | number | Page number where result was found (for multi-page) | ## Response example ```json theme={null} { "success": true, "result": { "organicResults": [ { "position": 1, "title": "Best Laptops for Programming in 2026", "link": "https://example.com/best-laptops-for-programming", "displayedLink": "example.com › best-laptops", "snippet": "We tested dozens of laptops, and these are the top picks for developers in 2026...", "page": 1 }, { "position": 2, "title": "Top 10 Developer Laptops Reviewed", "link": "https://example.com/dev-laptops-review", "displayedLink": "example.com › dev-laptops-review", "snippet": "An in-depth comparison of laptops for software developers.", "page": 1 } ] } } ``` # Google People Also Ask schema Source: https://cloro.dev/docs/api-reference/endpoint/google/people-also-ask Schema for People Also Ask questions returned by the Google Search endpoint, including expanded answers, source links, and optional AI Overview hydration. The [Google Search endpoint](/docs/api-reference/endpoint/monitor-google) returns **People Also Ask** items in `result.peopleAlsoAsk` by default — no separate API call. For use cases, pricing context, and copy-paste examples, see the [People Also Ask API](https://cloro.dev/serp-api/people-also-ask/) page on the product site. People also ask in Google Search Each item is classified by `type`: * **LINK**: cited answer with a snippet, title, and link * **AIOVERVIEW**: AI-generated summary. Set `include.paaAioverview` to `true` to hydrate these with `markdown` content and `sources` * **UNKNOWN**: unclassifiable item (returns `question` only) ## Example request ```json theme={null} { "query": "best laptops for programming", "country": "US", "include": { "paaAioverview": true } } ``` ## Item structure | Field | Type | Description | | ---------- | ------ | -------------------------------------------------------------------------------------- | | `question` | string | The question being asked | | `type` | string | Type of result (`AIOVERVIEW`, `LINK`, or `UNKNOWN`) | | `snippet` | string | Answer snippet (LINK type) | | `title` | string | Result title (LINK type) | | `link` | string | Result URL (LINK type) | | `markdown` | string | AI Overview markdown content (AIOVERVIEW type, when `include.paaAioverview` is `true`) | | `sources` | array | Cited sources (AIOVERVIEW type, when `include.paaAioverview` is `true`) | ### Hydrated sources Sources are only present on AIOVERVIEW-type items when `include.paaAioverview` is `true` and hydration succeeds. The shape matches the main [AI Overview sources](/docs/api-reference/endpoint/google/ai-overview). | Field | Type | Description | | ------------- | ------ | ------------------------------ | | `label` | string | Title of the source | | `url` | string | URL of the source | | `description` | string | Description of the source | | `position` | number | Position of source (1-indexed) | ## Response example ```json theme={null} { "success": true, "result": { "peopleAlsoAsk": [ { "question": "What laptop do most programmers use?", "type": "LINK", "snippet": "Most programmers prefer MacBooks or ThinkPads for their build quality and Linux compatibility.", "title": "Most popular laptops among developers", "link": "https://example.com/popular-dev-laptops" }, { "question": "Is 16GB RAM enough for programming?", "type": "AIOVERVIEW", "markdown": "16GB of RAM is generally sufficient for most programming tasks, including web development and mobile app development...", "sources": [ { "position": 1, "label": "Recommended RAM for developers", "url": "https://example.com/ram-for-developers", "description": "A breakdown of memory requirements by development workload." } ] }, { "question": "What does a programmer's setup look like?", "type": "UNKNOWN" } ] } } ``` # Google People Are Saying schema Source: https://cloro.dev/docs/api-reference/endpoint/google/people-are-saying Schema for cards from Google's 'What people are saying' SERP module returned by the Google Search endpoint, including source URLs, snippets, and themes. This section documents the **people are saying** data returned by the [Google Search endpoint](/docs/api-reference/endpoint/monitor-google): the cards Google's "What people are saying" module (sometimes labeled "Trending posts and discussions") shows on the SERP, typically pointing at Reddit, Quora, or other forum threads relevant to the query. Each entry in the `peopleAreSaying` array is one card, part of the same response so no separate API call is needed. For use cases, pricing context, and copy-paste examples, see the [People Are Saying API](https://cloro.dev/serp-api/people-are-saying/) page on the product site. People are saying module on the Google SERP The `peopleAreSaying` field is omitted from `result` when the module is absent — treat it as optional rather than expecting an empty array. ## Card structure | Field | Type | Description | | ---------- | ------ | ---------------------------------------------------------------------------------------- | | `position` | number | Position within the module (1-indexed) | | `title` | string | Card title as displayed on the SERP | | `link` | string | Destination URL the card points to | | `date` | string | Google's raw relative-time text (e.g., `"5 days ago"`, `"2 weeks ago"`). Not normalized. | ## Response example ```json theme={null} { "success": true, "result": { "peopleAreSaying": [ { "position": 1, "title": "Best running shoes 2026 - what runners are saying", "link": "https://www.reddit.com/r/running/comments/example/", "date": "5 days ago" }, { "position": 2, "title": "Honest review of the Gel-Nimbus 28 after 200 miles", "link": "https://www.quora.com/example", "date": "2 weeks ago" } ] } } ``` # Google Search product results schema Source: https://cloro.dev/docs/api-reference/endpoint/google/product-results Schema for merchant offers parsed from the Google Search right-rail product panel: direct URLs, prices, installments, stock, and delivery badges. Product results are the merchant offers in Google's **right-rail product panel** — merchant URL, price, installment terms, and stock, delivery and returns badges. The [Google Search endpoint](/docs/api-reference/endpoint/monitor-google) returns them as `result.productResults` automatically: no flag, no extra credits, since the panel is already in the HTML the scrape fetches. ```json theme={null} { "query": "iphone 17", "country": "US" } ``` Right-rail product panel on the Google SERP Each row becomes one entry in `stores[]`. This panel is separate from the main-column "Popular products" and "More products" grids, which remain [shopping cards](/docs/api-reference/endpoint/google/shopping-cards) — both can appear on the same SERP. Same shape as [AI Mode product results](/docs/api-reference/endpoint/aimode/product-results), which are opt-in via `include.expandProducts` and billed per cluster. **The panel is not guaranteed.** Whether Google renders it varies by session, not just by query — the same query can return `productResults` on one scrape and omit it on the next. When absent the field is omitted entirely, so treat it as optional rather than expecting an empty array. For dependable offers on a known product, use [AI Mode with `include.expandProducts`](/docs/api-reference/endpoint/aimode/product-results). ## Product result structure Each entry describes one product cluster and its merchant offers. | Field | Type | Description | | ----------------- | ------ | ------------------------------------------------------------------------- | | `title` | string | Product cluster title, as Google displayed it | | `stores` | array | Merchant offers. See [store structure](#store-structure) | | `aboutTheProduct` | object | `description` (Google's prose blurb) and `features` (bullets, when shown) | | `specifications` | object | Spec tables grouped by category. See [specifications](#specifications) | | `images` | array | `mainUrl` and `thumbnailUrl` per image, often identical | Only `title` is guaranteed — anything Google's panel omits is left out of the object rather than sent as `null`. Panels can also carry `brand`, `rating`, `reviews`, `priceRange`, `typicalPrices`, `variants`, `relatedProducts`, `userReviews`, `videos`, `discussionsAndForums` and `highlights`, emitted under those names when present, though most carry none of them. ### Specifications A category name mapped to a flat set of spec key/value pairs: ```json theme={null} { "specifications": { "general": { "Brand": "Sennheiser", "Noise cancellation": "Yes" } } } ``` Category names and spec keys arrive in the **response language** — a `country: "MX"` request returns `{"general": {"Marca": "Sennheiser"}}`. Iterate the object; don't read a hard-coded key like `specifications.general.Brand`. ### Store structure | Field | Type | Description | | --------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | Merchant name (e.g. `"Amazon"`) | | `link` | string | Merchant product URL, passed through from Google verbatim | | `price` | object | See [price shape](#price-shape). Omitted entirely when the displayed text can't be parsed | | `installments` | string | Installment terms as displayed (e.g. `"for 24 mo, $0 now, $798.96 total"`) | | `description` | string | Merchant's listing title, naming the exact SKU sold (e.g. `"iPhone 17 256GB White - Apple"`) — unlike `title`, which names the product overall | | `buyingOptions` | array | Merchant badges in display order — stock status, rating, delivery promise, returns window. Google varies these per merchant and locale, so they stay an unparsed list of strings | | `logo` | string | Merchant logo URL | | `shipping` | string | Shipping cost or promise as displayed | | `condition` | string | Item condition (e.g. `"New"`, `"Refurbished"`) | | `rating` | number | Merchant rating | | `reviews` | string | Merchant review count, abbreviated (e.g. `"384"`, `"2.3k"`) | Expect the first six on most offers and the rest only occasionally. Everything beyond `name` is optional and omitted when absent — Google shows different combinations per merchant, so an "Out of stock" listing may have no price or link at all. `link` is not validated against the cluster. Google occasionally serves a merchant URL for an unrelated SKU, so an offer's link can point at a different product than its `description` names. Verify against `description` if link correctness matters. ### Price shape | Field | Type | Description | | ---------- | ------ | --------------------------------------------------------------------------------------------------------------- | | `value` | number | Parsed numeric price; always present when `price` is emitted. On installment offers this is the monthly payment | | `currency` | string | ISO 4217 code (e.g. `"USD"`). Unrecognized symbols fall through as the glyph itself (e.g. `"R$"`) | | `raw` | string | Visible price text verbatim (e.g. `"$149.99"`) | `currency` here is an **ISO code** (`"USD"`), while [shopping cards](/docs/api-reference/endpoint/aimode/shopping-cards#price-and-oldprice) and [inline products](/docs/api-reference/endpoint/aimode/inline-products#price-and-oldprice) carry the raw symbol (`"$"`). Normalize before comparing across surfaces. ## Response example ```json theme={null} { "success": true, "result": { "organicResults": [...], "productResults": [ { "title": "iPhone 17", "stores": [ { "name": "Apple", "link": "https://www.apple.com/shop/buy-iphone/iphone-17-pro", "price": { "value": 999.00, "currency": "USD", "raw": "$999.00" }, "description": "iPhone 17 256GB White - Apple", "shipping": "Free delivery", "condition": "New", "buyingOptions": [ "In stock online", "4.8/5", "Free next-day delivery", "14-day returns" ] }, { "name": "Best Buy", "link": "https://www.bestbuy.com/site/apple-iphone-17-pro", "price": { "value": 41.62, "currency": "USD", "raw": "$41.62" }, "installments": "for 24 mo, $0 now, $998.88 total", "description": "Apple - iPhone 17 256GB - Black (Verizon)", "rating": 4.7, "reviews": "1.2k", "buyingOptions": [ "In stock online", "Free delivery by Sun", "30-day returns" ] } ] } ] } } ``` # Google related searches schema Source: https://cloro.dev/docs/api-reference/endpoint/google/related-searches Schema for related search suggestions returned by the Google Search endpoint, including query text and links that surface related user intent on the SERP. This section documents the **related searches** data returned by the [Google Search endpoint](/docs/api-reference/endpoint/monitor-google): the query suggestions Google surfaces at the bottom of the results page, based on the original query and user behavior signals, part of the Google response so no separate API call is needed. They appear in the order Google displays them on the SERP; array index `0` is the first suggestion shown. For use cases, pricing context, and copy-paste examples, see the [Google related searches API](https://cloro.dev/serp-api/related-searches/) page on the product site. Related searches in Google Search ## Example request ```json theme={null} { "query": "best laptops for programming", "country": "US" } ``` ## Related search structure | Field | Type | Description | | ------- | ------ | --------------------------------------- | | `query` | string | Related search query | | `link` | string | Google search URL for the related query | ## Response example ```json theme={null} { "success": true, "result": { "relatedSearches": [ { "query": "best laptops for programming under $1000", "link": "https://www.google.com/search?q=best+laptops+for+programming+under+%241000" }, { "query": "best laptop for python programming", "link": "https://www.google.com/search?q=best+laptop+for+python+programming" }, { "query": "best laptops for software developers 2026", "link": "https://www.google.com/search?q=best+laptops+for+software+developers+2026" } ] } } ``` # Google Search shopping cards schema Source: https://cloro.dev/docs/api-reference/endpoint/google/shopping-cards Shopping product information returned by the Google Search endpoint when 'Popular products' or 'More products' panels appear on the SERP. Shopping cards come from Google's organic shopping grids ("Popular products" and "More products"). The [Google Search endpoint](/docs/api-reference/endpoint/monitor-google) returns them as `result.shoppingCards` — no separate API call. For use cases, pricing context, and copy-paste examples, see the [Google Shopping scraper](https://cloro.dev/serp-api/shopping/) page on the product site. When a section header can be parsed, each card includes a `category` field naming its parent section, so you can tell apart coexisting shopping panels on the same SERP. The wire shape mirrors AI Mode's `shoppingCards`, with the same camelCase keys (`productLink`, `oldPrice`). `shoppingCards` is omitted from `result` when no shopping section is present — treat it as optional rather than expecting an empty array. Shopping cards on the Google SERP `productLink` is JS-hydrated by Google, so it is usually an empty string in the static HTML response — the card opens a Google-side sidebar overlay rather than navigating externally. The `title`, `price`, `store`, and other static fields remain populated. ## Shopping card structure | Field | Type | Description | | ------------- | ------ | ------------------------------------------------------------------------------------------------------------------------- | | `title` | string | Product title | | `position` | number | 1-indexed rank in the shopping grid, flat across the whole `shoppingCards` array (does not reset per `category` section). | | `productLink` | string | Direct product URL (usually empty due to JS-hydration) | | `category` | string | Parent section header (e.g., `"Popular products"`, `"More products"`) | | `price` | object | Structured pricing (see [Price shape](#price-and-oldprice) below) | | `oldPrice` | object | Original price before discount (same shape as `price`) | | `store` | string | Merchant/store name | | `rating` | number | Product rating | | `reviews` | string | Review count (e.g., "384", "2.3k") | | `thumbnail` | string | Product image URL | ### `price` and `oldPrice` Both fields carry a parsed and a raw representation: | Field | Type | Description | | ---------- | ------ | ----------------------------------------------------------------------------------- | | `value` | number | Numeric price. Present when the visible string parses unambiguously into a number. | | `currency` | string | Currency symbol (e.g. `$`, `£`, `€`). Present when a recognized symbol is detected. | | `raw` | string | Visible price text verbatim (e.g. `"$169.99"`). | `raw` is always present when `price` is emitted. ## Response example ```json theme={null} { "success": true, "result": { "shoppingCards": [ { "title": "ASICS Women's Gel-Nimbus 28", "position": 1, "productLink": "", "category": "More products", "price": { "value": 169.99, "currency": "$", "raw": "$169.99" }, "store": "DICK'S Sporting Goods", "rating": 4.5, "reviews": "384" } ] } } ``` # Google Search sponsored ads schema Source: https://cloro.dev/docs/api-reference/endpoint/google/sponsored-ads Schema for sponsored ad results returned by the Google Search endpoint, including text ads and shopping-style cards from top-of-page and side carousels. Sponsored ads are the paid placements Google injects into the SERP: classic text ads (top and bottom of the page) and shopping-style sponsored cards (right-hand-side and top-of-page carousels). They arrive in the `ads[]` array of the [Google Search endpoint](/docs/api-reference/endpoint/monitor-google) response — no separate API call, no opt-in. For use cases, pricing context, and copy-paste examples, see the [Google ads scraper](https://cloro.dev/serp-api/ads/) page on the product site. Sponsored ads in Google Search ## Example request ```json theme={null} { "query": "running shoes", "country": "US" } ``` ## Ad types Each ad carries a `type` discriminator with two values: * **`RESULT`** — plain text ad. Displayed at the top or bottom of the main column with ad copy, sitelinks, and a normal destination URL. * **`SHOPPING_CARD`** — shopping-style sponsored card. Carries a product image, price, optional MSRP / `oldPrice`, merchant name, and a Google-redirected `aclk?` destination URL. `SHOPPING_CARD` ads surface on three SERP positions, distinguished by `blockPosition` and `category`: | Surface | `blockPosition` | `category` examples | | -------------------------------------------- | --------------- | -------------------------------------------------------------- | | Right-hand-side PLA carousel | `rhs` | `Sponsored products` | | Top-of-page PLA carousel | `top` | `Sponsored products` | | Top-of-page sponsored shopping-card carousel | `top` | `Sponsored vehicles`, `Sponsored products`, `Sponsored hotels` | Sponsored products carousel on the Google SERP Do not confuse `type: SHOPPING_CARD` ads with the organic [`shoppingCards`](/docs/api-reference/endpoint/google/shopping-cards) field. Sponsored cards live in `ads[]`, are paid placements, and use Google `aclk?`-redirected URLs. Organic shopping cards live in `shoppingCards[]`, come from the "Popular products" / "More products" grids, and use JS-hydrated `productLink` URLs that are usually empty in the static HTML response. ## Sponsored ad structure Fields shared across all ad types: | Field | Type | Description | | --------------- | ------ | --------------------------------------------------------------------------- | | `position` | number | Position within the ad block (1-indexed) | | `blockPosition` | string | Where the ad appeared: `top`, `bottom`, `middle`, or `rhs` (see "Ad types") | | `type` | string | `RESULT` for text ads; `SHOPPING_CARD` for shopping-style cards | | `title` | string | Ad title | | `url` | string | Destination URL of the ad (Google `aclk?` redirect for `SHOPPING_CARD`) | | `page` | number | Page number where the ad was found | | `description` | string | Ad description text (see note below) | For `type: RESULT` items, `description` is classic ad copy. For `type: SHOPPING_CARD` items it carries category-specific subtitle fragments joined with `·` — for example `Used - 94k miles · Greeley` on a "Sponsored vehicles" card. Fields specific to `type: RESULT` text ads: | Field | Type | Description | | -------------- | ------ | ------------------------------------ | | `displayedUrl` | string | Formatted URL as displayed in the ad | | `domain` | string | Domain name of the advertiser | | `sitelinks` | array | Sitelinks displayed under the ad | Fields specific to `type: SHOPPING_CARD` ads: | Field | Type | Description | | ---------- | ------ | ----------------------------------------------------------------------- | | `category` | string | Carousel header label (e.g. `Sponsored products`, `Sponsored vehicles`) | | `price` | object | Product price (see [Price shape](#price-shape) below) | | `oldPrice` | object | Original price before discount / MSRP, same shape as `price` | | `store` | string | Merchant or dealer name | | `imageUrl` | string | Hero image URL served by Google's `encrypted-tbn` CDN | ### Price shape `price` and `oldPrice` carry both a parsed and a raw representation: | Field | Type | Description | | ---------- | ------ | ------------------------------------------------------------------------------------ | | `value` | number | Numeric price. Present when the visible string parses unambiguously into a number. | | `currency` | string | Currency symbol (e.g. `$`, `£`, `€`). Present when a recognized symbol is detected. | | `raw` | string | Visible price text verbatim (e.g. `"$1,199"`, `"$0 down with 24 monthly payments"`). | `raw` is always present when `price` is emitted. Installment / down-payment labels (common on phone-contract cards) parse the leading `"$0"` as `value: 0` — use `raw` to disambiguate genuine free items from down-payment phrasing. ### Ad sitelinks | Field | Type | Description | | ------------- | ------ | -------------------- | | `url` | string | Sitelink URL | | `title` | string | Sitelink title | | `description` | string | Sitelink description | ## Response example ```json theme={null} { "success": true, "result": { "ads": [ { "position": 1, "blockPosition": "top", "type": "RESULT", "title": "Running Shoes Sale - Up to 40% Off", "url": "https://www.acmeshoes.com/running", "page": 1, "displayedUrl": "www.acmeshoes.com/running", "domain": "acmeshoes.com", "description": "Shop top brands of running shoes with free shipping on orders over $50.", "sitelinks": [ { "url": "https://www.acmeshoes.com/running/road", "title": "Road Running", "description": "Lightweight shoes for paved surfaces." }, { "url": "https://www.acmeshoes.com/running/trail", "title": "Trail Running", "description": "Durable shoes for off-road terrain." } ] }, { "position": 1, "blockPosition": "top", "type": "SHOPPING_CARD", "category": "Sponsored vehicles", "title": "2021 Ford F-150 XLT", "url": "https://www.google.com/aclk?sa=L&ai=...", "page": 1, "description": "Used - 94k miles · Greeley", "price": { "value": 32915, "currency": "$", "raw": "$32,915" }, "oldPrice": { "value": 35900, "currency": "$", "raw": "$35,900" }, "store": "Acme Ford", "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:..." }, { "position": 1, "blockPosition": "rhs", "type": "SHOPPING_CARD", "category": "Sponsored products", "title": "Nike Pegasus 41", "url": "https://www.google.com/aclk?sa=L&ai=...", "page": 1, "price": { "value": 139.99, "currency": "$", "raw": "$139.99" }, "store": "Nike.com", "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:..." } ] } } ``` # Grok sources schema Source: https://cloro.dev/docs/api-reference/endpoint/grok/sources Schema for source citations returned by the Grok endpoint, enriched with preview text, creator details, site information, image URLs, and publication dates. This section documents the **sources** data returned by the [Grok endpoint](/docs/api-reference/endpoint/monitor-grok): source citations enriched with preview text, creator details, site information and image URLs, part of the Grok response so no separate API call is needed. They follow the [common sources structure](/docs/guides/making-requests/sync#sources-array-structure) (`position`, `url`, `label`, `description`) plus the Grok-specific fields below. For use cases, pricing context, and copy-paste examples, see the [Grok sources API](https://cloro.dev/grok/sources/) page on the product site. Sources in Grok ## Example request ```json theme={null} { "prompt": "latest developments in quantum computing", "country": "US" } ``` ## Grok-specific source fields | Field | Type | Description | | ------------------ | ------ | ----------------------------------------- | | `preview` | string | Text snippet preview from the source | | `searchEngineText` | string | Search engine display text for the source | | `siteName` | string | Website name | | `metadataTitle` | string | Source page metadata title | | `creator` | string | Content creator or author | | `image` | string | URL to preview image from the source | | `favicon` | string | URL to website favicon | ## Response example ```json theme={null} { "success": true, "result": { "text": "Quantum computing has progressed in 2026...", "sources": [ { "position": 1, "url": "https://example.com/quantum-2026", "label": "Quantum Computing in 2026", "description": "An overview of recent advances in quantum hardware and algorithms.", "preview": "Researchers at MIT and IBM announced a 1,000-qubit processor that...", "searchEngineText": "Quantum Computing in 2026 - Recent Breakthroughs", "siteName": "Example Tech", "metadataTitle": "Quantum Computing in 2026 | Example Tech", "creator": "Jane Doe", "image": "https://example.com/quantum-cover.jpg", "favicon": "https://example.com/favicon.ico" } ] } } ``` # Extract Google AI Mode Source: https://cloro.dev/docs/api-reference/endpoint/monitor-aimode api-reference/openapi.json POST /v1/monitor/aimode Extract structured data from AI Mode about your brand, products, or any topic across various regions Extract structured data from Google AI Mode, with automatic detection of [places](/docs/api-reference/endpoint/aimode/places), [shopping cards](/docs/api-reference/endpoint/aimode/shopping-cards), [ads](/docs/api-reference/endpoint/aimode/ads), [inline products](/docs/api-reference/endpoint/aimode/inline-products), [videos](/docs/api-reference/endpoint/aimode/videos), and [map entries](/docs/api-reference/endpoint/aimode/map). Merchant offers behind each product cluster are available on request as [product results](/docs/api-reference/endpoint/aimode/product-results). **Web search enabled** This endpoint uses AI Mode's default interface, which combines web search results with AI-generated responses. ## Request parameters **Required parameters:** * `prompt` (string): The query to send to AI Mode (1-10,000 characters) * `country` (string): ISO 3166-1 alpha-2 country code (uppercase) for localized results. Required — there is no default **Optional parameters:** * `location` (string): Google canonical location name for geo-targeted results (e.g., `New York,New York,United States`). See [Google's geo target list](https://developers.google.com/google-ads/api/reference/data/geotargets) for all \~100,000 supported locations. Mutually exclusive with `uule`. When both `location` and `uule` are omitted, cloro defaults `location` to the requested country's canonical name (e.g. `Portugal` for `country: "PT"`) so results pin to the country instead of the proxy's exit IP. Pass `location` or `uule` explicitly when you need city- or region-level precision * `uule` (string): Pre-encoded Google UULE string for precise geo-targeting. Use this when you have a pre-built UULE value instead of a location name. Mutually exclusive with `location` * `device` (string): Device type for search results. Options: `desktop` (default), `mobile` * `include.markdown` (boolean): Include markdown-formatted response. Defaults to `false` * `include.html` (boolean): Include raw HTML response. Defaults to `false` * `include.expandProducts` (boolean): Fetch merchant offers for the product clusters in the response (up to 6) and return them as [`result.productResults`](/docs/api-reference/endpoint/aimode/product-results). Opt-in, off by default **Additional credit cost** `include.expandProducts` charges **+1 credit per product cluster** returned in `result.productResults`, capped at **+6**. The surcharge is applied after the scrape, so it scales with what Google actually surfaces: clusters skipped by the cap, or dropped because their product viewer could not be fetched, are not charged. Requests without the flag are unaffected. See [providers](/docs/guides/providers#ai-mode-additional-features) for pricing details. ## Response objects | Section | Description | | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Sources](/docs/api-reference/endpoint/aimode/sources) | Source citations referenced in the response | | [Places](/docs/api-reference/endpoint/aimode/places) | Inline place cards when location intent is detected | | [Shopping cards](/docs/api-reference/endpoint/aimode/shopping-cards) | Product carousel when shopping intent is detected | | [Ads](/docs/api-reference/endpoint/aimode/ads) | Sponsored ad section when commercial intent is detected | | [Inline products](/docs/api-reference/endpoint/aimode/inline-products) | Individual product references embedded in the response text | | [Videos](/docs/api-reference/endpoint/aimode/videos) | Inline video cards (currently YouTube) embedded in the response | | [Map](/docs/api-reference/endpoint/aimode/map) | GPS-enriched location data when map-aware results are surfaced | | [Product results](/docs/api-reference/endpoint/aimode/product-results) | Merchant offers behind each product cluster — direct URL, price, installments, stock, delivery, returns. Returned only when `include.expandProducts` is set | ## Response schema Includes [common response fields](/docs/guides/making-requests/sync#common-response-fields) plus: ### Core response fields | Field | Type | Description | | ----------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `result.text` | string | AI Mode's response text | | `result.markdown` | string | Response formatted in Markdown (included when `include.markdown` is true). **Note:** this field is generated by converting Google's live DOM to markdown — order can shift as Google updates its DOM. | | `result.sources` | array | Array of [sources](/docs/api-reference/endpoint/aimode/sources) referenced in the response | | `result.places` | array | Array of [places](/docs/api-reference/endpoint/aimode/places) (when location intent detected) | | `result.shoppingCards` | array | Array of [shopping cards](/docs/api-reference/endpoint/aimode/shopping-cards) (when shopping intent detected) | | `result.ads` | object | [Ad section](/docs/api-reference/endpoint/aimode/ads) wrapper with title and ads array (when commercial intent detected) | | `result.inlineProducts` | array | Array of [inline products](/docs/api-reference/endpoint/aimode/inline-products) found within the response text | | `result.videos` | array | Array of [videos](/docs/api-reference/endpoint/aimode/videos) embedded in the response (currently YouTube) | | `result.map` | array | Array of [map entries](/docs/api-reference/endpoint/aimode/map) with GPS coordinates (when map-aware results surfaced) | | `result.productResults` | array | Array of [product results](/docs/api-reference/endpoint/aimode/product-results) with merchant offers per product cluster. Returned only when `include.expandProducts: true` is set on the request | ## Usage examples ### City-level geo-targeting Pair `location` with `country` to target a city or region. It accepts [Google canonical location names](https://developers.google.com/google-ads/api/reference/data/geotargets). ```json theme={null} { "prompt": "best restaurants nearby", "country": "US", "location": "New York,New York,United States" } ``` ### Expanding merchant offers ```json theme={null} { "prompt": "best wireless headphones under $200", "country": "US", "include": { "expandProducts": true } } ``` cloro follows each cluster's product viewer and returns direct merchant URLs, prices, installment terms, and delivery/returns badges as [`result.productResults`](/docs/api-reference/endpoint/aimode/product-results). Clusters are deduplicated across `shoppingCards` and `inlineProducts`, and expansion is capped at 6 per scrape. ### UULE geo-targeting When you generate your own UULE values, pass `uule` instead of `location` — the two are mutually exclusive: ```json theme={null} { "prompt": "best restaurants nearby", "country": "US", "uule": "w+CAIQICIeV2VzdCBOZXcgWW9yayxOZXcgSmVyc2V5" } ``` # Extract ChatGPT Source: https://cloro.dev/docs/api-reference/endpoint/monitor-chatgpt api-reference/openapi.json POST /v1/monitor/chatgpt Extract structured data from ChatGPT — answer text, cited sources, shopping cards, brand entities, and ads — for any prompt across supported regions Extract structured data from ChatGPT: shopping cards, brand entities, map entries, raw response data, and query fan-out. **Web search enabled** This endpoint enables ChatGPT's web search mode on every request, so responses include current information from the web with source citations. ## Request parameters Uses [common parameters](/docs/guides/making-requests/sync#common-parameters). All ChatGPT-specific `include.*` flags are listed in the auto-generated request schema below. **Additional credit cost** Enabling any of `include.rawResponse`, `include.searchQueries`, `include.ads`, or `include.shopping` (or any combination) adds +2 credits to the base cost. ## Response objects | Section | Description | | ------------------------------------------------------------------ | ---------------------------------------------------------------- | | [Sources](/docs/api-reference/endpoint/chatgpt/sources) | Source citations with publication dates and footnote indicators | | [Shopping cards](/docs/api-reference/endpoint/chatgpt/shopping-cards) | Structured product information with pricing, ratings, and offers | | [Inline products](/docs/api-reference/endpoint/chatgpt/inline-products) | Individual product references with rationale and themed reviews | | [Entities](/docs/api-reference/endpoint/chatgpt/entities) | Named entities like products, brands, and concepts | | [Map entries](/docs/api-reference/endpoint/chatgpt/map) | Business and place information from Yelp and Google | | [Citation pills](/docs/api-reference/endpoint/chatgpt/citation-pills) | Inline citations with rich metadata | | [Ads](/docs/api-reference/endpoint/chatgpt/ads) | Advertiser branding and product carousel cards | ## Response schema Includes [common response fields](/docs/guides/making-requests/sync#common-response-fields) plus: | Field | Type | Description | | ------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `result.rawResponse` | array | Array of ChatGPT's streamed response events. When `include.shopping` is also true, this additionally includes the `product_update` SSE events fetched for each inline product / shopping-card product (included when `include.rawResponse` is true) | | `result.searchQueries` | array | Array of search model queries (fan-out queries) ChatGPT used internally to gather information (included when `include.searchQueries` is true) | | `result.mapSearchQueries` | array | Array of map search queries ChatGPT fired at the maps tool to build the [map entries](/docs/api-reference/endpoint/chatgpt/map) (included when `include.searchQueries` is true). Distinct from `searchQueries`, which are the web-search fan-out queries | | `result.ads` | array | Array of [ads](/docs/api-reference/endpoint/chatgpt/ads) displayed in ChatGPT response (included when `include.ads` is true) | | `result.shoppingCards` | array | Array of [shopping/product cards](/docs/api-reference/endpoint/chatgpt/shopping-cards) extracted from response (included when `include.shopping` is true) | | `result.inlineProducts` | array | Array of [inline products](/docs/api-reference/endpoint/chatgpt/inline-products) with pricing and offers (included when `include.shopping` is true) | | `result.entities` | array | Array of [entities](/docs/api-reference/endpoint/chatgpt/entities) extracted from response (when available) | | `result.map` | array | Array of [business/place map entries](/docs/api-reference/endpoint/chatgpt/map) extracted from response (when available) | | `result.citationPills` | array | Array of [inline citation pills](/docs/api-reference/endpoint/chatgpt/citation-pills) extracted from response (when available) | | `result.sources` | array | Array of [sources](/docs/api-reference/endpoint/chatgpt/sources) referenced in the response. See [Sources](/docs/api-reference/endpoint/chatgpt/sources) for ChatGPT-specific fields | | `result.model` | string | The ChatGPT model used to generate the response | ## Common questions ### How does cloro retrieve ChatGPT responses? cloro does not call the ChatGPT API. Each request runs through a real browser session against ChatGPT's web interface, routed through a proxy in the country (and, where supported, state) you specify. The `result.*` fields are extracted from that page; the raw SSE events are available via `include.rawResponse: true`. See [How does cloro retrieve data from AI providers?](/docs/guides/providers#how-does-cloro-retrieve-data-from-ai-providers) for the implications (model routing, auth walls, geo behaviour). ### Can I get the search queries that ChatGPT uses? Yes. Set `include.searchQueries: true` to get the fan-out queries ChatGPT runs internally. The same flag also returns `result.mapSearchQueries`: the queries ChatGPT sends to the maps tool to build the map block, separate from the web-search fan-out in `result.searchQueries`. ### Are query fan-outs available for all ChatGPT responses? ChatGPT uses `gpt-5-3` and `gpt-5-3-mini` that include fan-out queries; newer models may not include explicit fan-outs. ChatGPT automatically selects which model to use for each request, so we cannot control the presence of query fan-outs. ### Are query fan-outs available for other providers? For a complete comparison of query fan-out support across all providers, see the [Providers & pricing guide](/docs/guides/providers#query-fan-out-support). ### Why is a fan-out query a single long string instead of separated keywords? That is expected. ChatGPT's search model decides the shape of each fan-out query, and often emits a single long natural-language query rather than a keyword list. Length and structure vary between runs, even for the same prompt, and cloro returns the queries exactly as ChatGPT generates them. [This LinkedIn post by Stefan Landwehr](https://www.linkedin.com/posts/landwehr_i-recently-wrote-about-how-chatgpt-fanout-activity-7421889498805891072-Ap9Q/) documents the same behaviour. ### Why aren't shopping cards appearing in my responses? Shopping cards only appear when the prompt is related to products or shopping: ```bash theme={null} # ✅ Good - shopping related "best laptops under $1000" "compare iPhone vs Samsung" "top rated headphones 2026" # ❌ Bad - not shopping related "what is the capital of France" "how does photosynthesis work" ``` Even shopping queries don't always return cards — it depends on what ChatGPT finds and how it formats the response. ### Which providers support shopping cards? For a complete comparison of shopping card support across all providers, see the [Providers & pricing guide](/docs/guides/providers#shopping-cards-support). ### My non-English prompts are returning English fan-out queries. Is that expected? Yes. ChatGPT may generate some or all of its internal search queries (`result.searchQueries`) in English regardless of prompt language — a roughly 50/50 English/local-language split has been observed. This is upstream behavior; cloro returns the queries as generated. For multilingual GEO monitoring, filter or group by query language. ### Why are `result.model`, `searchQueries`, or `mapSearchQueries` sometimes empty? OpenAI serves a share of logged-out ChatGPT traffic through a **mobile-web** response format that streams the answer as HTML fragments instead of the usual event stream. Those responses carry no stream metadata, so `result.model`, `result.searchQueries`, and `result.mapSearchQueries` come back empty and `result.sources` is shorter. The answer `text` / `markdown` and inline `citationPills` are unaffected. "Mobile-web" is OpenAI's response format, **not** the requesting device or any parameter you send. OpenAI decides the format per request, so identical requests can land in either — treat these fields as possibly empty. See the [21st July 2026 changelog entry](/docs/changelog) for the full breakdown. ### What's the difference between shopping cards and inline products? Shopping cards are grouped collections of products displayed together (like a carousel): * Contain multiple products * Include category tags * For browsing multiple options Inline products are individual product references: * Single product per entry * Include pricing, offers, images, and ratings * Can be embedded inline in text, comparison tables, or featured displays * Have rendering hints (inline, hero, block) Both are extracted only when `include.shopping: true` is set in the request. # Extract Microsoft Copilot Source: https://cloro.dev/docs/api-reference/endpoint/monitor-copilot api-reference/openapi.json POST /v1/monitor/copilot Extract structured data from Microsoft Copilot about your brand, products, or any topic across various regions Extract structured data from Microsoft Copilot, including [shopping cards](/docs/api-reference/endpoint/copilot/shopping-cards) for product tracking. **Web search enabled** This endpoint uses Copilot's default interface, which always performs web searches for all requests to provide real-time information with source citations. ## Request parameters Uses [common parameters](/docs/guides/making-requests/sync#common-parameters). **Endpoint-specific options:** * `include.markdown` (boolean): Include markdown-formatted response. Defaults to `false` * `include.rawResponse` (boolean): Include raw streaming response events. Defaults to `false` ## Response objects The response includes the following sections. See each subpage for the full schema and examples. | Section | Description | | ---------------------------------------------------------------- | ------------------------------------------------------ | | [Sources](/docs/api-reference/endpoint/copilot/sources) | Source citations referenced in the response | | [Shopping cards](/docs/api-reference/endpoint/copilot/shopping-cards) | Product cards with pricing, ratings, and seller info | | [Map entries](/docs/api-reference/endpoint/copilot/map-entries) | Business and place information with reviews and photos | ## Response schema Includes [common response fields](/docs/guides/making-requests/sync#common-response-fields) plus: | Field | Type | Description | | ---------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `result.sources` | array | Array of [sources](/docs/api-reference/endpoint/copilot/sources) referenced in the response | | `result.shoppingCards` | array | Array of [shopping card](/docs/api-reference/endpoint/copilot/shopping-cards) objects with product information (automatically included when available) | | `result.map` | array | Array of [map entry](/docs/api-reference/endpoint/copilot/map-entries) objects with business/place information (automatically included when available) | | `result.searchQueries` | array | Array of query fan-out — the web search queries Copilot ran while generating the response (automatically included when at least one is available, no surcharge) | | `result.markdown` | string | Response formatted in Markdown (included when `include.markdown` is true) | | `result.rawResponse` | array | Array of Copilot's streamed response events (included when `include.rawResponse` is true) | # Extract Google Gemini Source: https://cloro.dev/docs/api-reference/endpoint/monitor-gemini api-reference/openapi.json POST /v1/monitor/gemini Extract structured data from Google Gemini — generated answer text and cited sources for any prompt, with Markdown and HTML export formats Extract structured data from Google's Gemini AI: the generated text response, citations and sources, with Markdown and HTML export formats. **Web search behavior** This endpoint uses Gemini's default interface. Gemini decides for itself whether a query needs current information, so web search and source citations may or may not appear. ## Request parameters Uses [common parameters](/docs/guides/making-requests/sync#common-parameters). **Endpoint-specific options:** * `include.rawResponse` (boolean): Include raw streaming response events. Defaults to `false` ## Response objects | Section | Description | | ------------------------------------------------- | --------------------------------------------- | | [Sources](/docs/api-reference/endpoint/gemini/sources) | Source citations with contextual descriptions | ## Response schema Includes [common response fields](/docs/guides/making-requests/sync#common-response-fields) plus: | Field | Type | Description | | -------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------- | | `result.text` | string | The main text content of the response | | `result.sources` | array | Array of [sources](/docs/api-reference/endpoint/gemini/sources) cited in the response | | `result.markdown` | string | Response formatted in Markdown (included when `include.markdown` is true) | | `result.html` | string | A URL to the full HTML of the response (included when `include.html` is true, [expires after 24 hours](/docs/guides/output-formats)) | | `result.rawResponse` | array | Array of Gemini's streamed response events (included when `include.rawResponse` is true) | ## Example usage ```json theme={null} { "prompt": "Explain quantum entanglement", "country": "US", "include": { "markdown": true } } ``` ## Common questions ### Some responses come back with no answer. How often, and what should I do? Under 2% of successful Gemini responses contain no usable answer — Gemini returns a short canned message instead of content, with an empty `sources` array. See [empty responses from AI providers](/docs/guides/error-handling#empty-responses-from-ai-providers) for how to detect them and why matching on the message text does not work. The breakdown below comes from our own continuous Gemini monitoring. "Success on retry" is how often the same prompt returned a full answer on a later run: | Type | Share of empty responses | Success on retry | | -------------------------------- | ------------------------ | ---------------- | | Safety refusal | 73% | 62% | | Age-restricted or policy refusal | 18% | 36% | | Truncated answer | 5% | 89% | | Capability refusal | 2% | 96% | | Provider error | 1% | 98% | | Account notice | 1% | 93% | Retrying once resolves roughly 60% of them overall, which brings the effective rate to well under 1%. Age-restricted refusals are the exception — they follow the subject of the prompt, so retrying converts far fewer of them. These figures move as Google changes Gemini's models and safety policies. Use them to size a retry budget, not as a guarantee. ### How does cloro retrieve Gemini responses? cloro does not call the Gemini API. Each request runs through a real browser session against Gemini's web interface, routed through a proxy in the country you specify. The `result.*` fields are extracted from that page; the raw streamed events are available via `include.rawResponse: true`. See [How does cloro retrieve data from AI providers?](/docs/guides/providers#how-does-cloro-retrieve-data-from-ai-providers) for the implications (model routing, auth walls, geo behaviour). # Extract Google Search Source: https://cloro.dev/docs/api-reference/endpoint/monitor-google api-reference/openapi.json POST /v1/monitor/google Extract structured data from Google Search including organic results, People Also Ask, related searches, and optional AI Overview Extract structured data from Google Search results: organic results, sponsored ads, People Also Ask questions, related searches, and optional [AI Overview data](/docs/api-reference/endpoint/google/ai-overview). When Google shows a right-rail product panel for the query, its merchant offers are returned as [product results](/docs/api-reference/endpoint/google/product-results). AI Overview supports a different set of countries than Google Search. Check with the [countries endpoint](/docs/api-reference/endpoint/countries) using `model=aioverview`, not `model=google`. ## Request parameters **Required parameters:** Describe the search in one of two ways — either the standard fields, or a Google search URL: * `query` (string) **and** `country` (string): The search query to execute on Google (1-10,000 characters), plus the ISO 3166-1 alpha-2 country code (uppercase) for localized results. There is no default country * `url` (string): A complete Google search URL to run instead. See [searching by URL](#searching-by-url) **Optional parameters:** * `location` (string): Google canonical location name for geo-targeted results (e.g., `New York,New York,United States`). See [Google's geo target list](https://developers.google.com/google-ads/api/reference/data/geotargets) for all \~100,000 supported locations. Mutually exclusive with `uule`. When both `location` and `uule` are omitted, cloro defaults `location` to the requested country's canonical name (e.g. `Portugal` for `country: "PT"`) so results pin to the country instead of the proxy's exit IP. Pass `location` or `uule` explicitly when you need city- or region-level precision * `uule` (string): Pre-encoded Google UULE string for precise geo-targeting. Use this when you have a pre-built UULE value instead of a location name. Mutually exclusive with `location` * `device` (string): Device the search is run from. Options: `desktop` (default), `ios`, `android`, and `mobile` (an alias for `android`). `ios` emulates Safari on iPhone and `android` emulates Chrome on Android; all three phone values return the mobile SERP layout. * `pages` (integer): Number of search results pages to scrape (1-10). Defaults to `1` * `include.html` (boolean): Include raw HTML response. Defaults to `false` * `include.aioverview` (object): Include Google AI Overview. Set `markdown: true` for markdown formatting. Defaults to `false` (not included) * `include.paaAioverview` (boolean): Hydrate AI-Overview-type People Also Ask items with markdown content and cited sources. Defaults to `false` **Additional credit cost** Enabling `include.aioverview` or `include.paaAioverview` adds +2 credits. The add-on is inclusive: enabling both still adds +2 credits total, not +4. Each page beyond the first adds +2 credits (e.g. `pages: 3` adds +4, `pages: 10` adds +18). Read the exact charge for a request from the `X-Credits-Charged` response header. See [providers](/docs/guides/providers#google-search-multi-page-pricing) for full pricing details. ## Searching by URL If you already assemble your own Google search URLs, send one as `url` instead of `query` and the targeting fields: ```json theme={null} { "url": "https://www.google.com/search?q=canon+ij+scanner+software&hl=en&gl=us&num=30" } ``` That is the whole body. `country` is derived from the URL's `gl`. Pass `country` explicitly to override it, or when your URL has no `gl`. `url` is mutually exclusive with `query`, `location`, `uule`, and `pages` — the URL owns those values, and sending both is a `400`. `device` and the `include.*` flags stay top level and behave exactly as they do in the standard shape. The response schema is identical, and so is the cost: a URL request is charged as the equivalent standard request, so `num=30` resolves to 3 pages and is billed as `pages: 3`. ### What cloro reads from the URL cloro supports a fixed set of query parameters: | Parameter | Effect | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `q` | The search query. Required — a URL without it is rejected | | `gl` | Sets `country` when you don't pass one. Uppercased, and must be a [supported country](/docs/api-reference/endpoint/countries) | | `hl` | Interface language for the results. Overrides the language cloro would otherwise derive from the country, so `hl` and `gl` can differ — a Portuguese-language SERP in a US locale, for example | | `uule` | Geo-targets the search, same as the top-level `uule` | | `num` | Requested result depth, converted to whole pages of 10 and capped at 10 pages (`num=30` → 3 pages). Google itself caps `num` at 10 on web search, so cloro paginates to reach the depth you asked for. Omit it and you get a single page | | `start` | Result offset to begin from. See [starting from a given rank](#starting-from-a-given-rank) | | `tbs` | Google's search-refinement filter, applied as written — including compound values such as `cdr:1,cd_min:1/1/2026,cd_max:2/1/2026` | | `safe` | SafeSearch setting, applied as written (`active` or `off`) | Parameters outside this set — `glp`, `lr` and the like — don't cause a rejection, but they're dropped rather than applied to the search. ### Starting from a given rank `start` sets the rank your first page begins at, and results are labelled with the SERP page and position they actually came from. A URL with `start=20` returns Google's third page, with `position` counting from 21 and `page` reported as `3`: ```json theme={null} { "url": "https://www.google.com/search?q=best+seo+tools&gl=us&start=20" } ``` With no `num`, that returns a single page — the usual shape if you drive pagination yourself by issuing one request per `start` value. Add `num` to fetch several pages from the offset in one request: `start=20` with `num=30` returns ranks 21-30, 31-40 and 41-50. ### Rejected URLs A URL is rejected with a `400` when it: * Is not an absolute `http`/`https` URL * Is not on a Google web search host — `google.com`, `google.co.uk`, `www.google.de` and similar are accepted; subdomains such as `news.google.com` are not * Has a path other than `/search` * Has no `q`, or an empty one * Carries `tbm`, which targets a Google vertical with its own endpoint and pricing. For `tbm=nws`, use [Google News](/docs/api-reference/endpoint/monitor-google-news) instead * Has a `gl` cloro doesn't support, with no explicit `country` to fall back on * Has neither `gl` nor an explicit `country` — cloro won't guess a target ## Requesting AI Overview ```json theme={null} { "query": "best laptops for programming", "include": { "aioverview": { "markdown": true } } } ``` `markdown` (boolean, defaults to `false`) returns the AI Overview formatted as Markdown. Without the `aioverview` object, no AI Overview data is returned. ## Error handling **AI Overview behavior** When Google returns no AI Overview for a query (after several retries), you get `aioverview: null` in a 200 response, with all other search data intact. In a region where AI Overview isn't supported, `include.aioverview` fails with an `UnsupportedInputError` instead — a region-level error, not a query failure. ```json theme={null} { "success": true, "result": { "organicResults": [...], "peopleAlsoAsk": [...], "relatedSearches": [...], "aioverview": null } } ``` ## Response objects | Section | Field | Description | | ------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [Organic results](/docs/api-reference/endpoint/google/organic-results) | `result.organicResults` | Standard non-paid search results from Google | | [Sponsored ads](/docs/api-reference/endpoint/google/sponsored-ads) | `result.ads` | Paid ad placements with sitelinks | | [People Also Ask](/docs/api-reference/endpoint/google/people-also-ask) | `result.peopleAlsoAsk` | Related questions with link or AI Overview answers | | [Related searches](/docs/api-reference/endpoint/google/related-searches) | `result.relatedSearches` | Related query suggestions | | [Local Pack](/docs/api-reference/endpoint/google/local-pack) | `result.localResults` | Map-backed "3-pack" of local businesses on local-intent queries — when present | | [AI Overview](/docs/api-reference/endpoint/google/ai-overview) | `result.aioverview` | Google's AI-generated summary with sources, videos, and ads (when requested) | | [Knowledge Graph](/docs/api-reference/endpoint/google/knowledge-graph) | `result.knowledgeGraph` | Structured entity panel (person, place, organization, film, etc.) — when present | | [Product results](/docs/api-reference/endpoint/google/product-results) | `result.productResults` | Merchant offers from the SERP right-rail product panel — direct URL, price, installments, stock, delivery, returns. Emitted when the panel appears, no flag needed, no extra credits | `result.html` is also returned when `include.html` is `true`. It is an **array** of URLs — one per scraped page, so a `pages: 3` request returns three entries — not a single URL string. Each URL [expires after 24 hours](/docs/guides/output-formats). `result.knowledgeGraph`, `result.shoppingCards`, `result.productResults`, `result.peopleAreSaying`, and `result.localResults` are omitted from `result` when the corresponding panel does not appear on the SERP. Treat them as optional rather than expecting `null` or an empty array. ## Usage examples ### Scrape multiple pages ```json theme={null} { "query": "best laptops for programming", "pages": 3, "country": "US" } ``` Organic results, People Also Ask questions, and related searches from all 3 pages are combined into a single response. Each `peopleAlsoAsk` entry carries a `page` field marking which page it appeared on. ### City and state-level geo-targeting `location` accepts any Google canonical location name, including states and regions — not just cities. ```json theme={null} { "query": "best restaurants", "country": "US", "location": "New York,New York,United States" } ``` For state-level targeting: ```json theme={null} { "query": "best electricians", "country": "US", "location": "California,United States" } ``` ### UULE geo-targeting When you generate your own UULE values, pass `uule` instead of `location` — the two are mutually exclusive: ```json theme={null} { "query": "best restaurants", "country": "US", "uule": "w+CAIQICIeV2VzdCBOZXcgWW9yayxOZXcgSmVyc2V5" } ``` ### Hydrate People Also Ask with AI Overview content AI-Overview-type People Also Ask items come back with markdown content and cited sources: ```json theme={null} { "query": "best laptops for programming", "include": { "paaAioverview": true } } ``` Combine it with AI Overview extraction: ```json theme={null} { "query": "best laptops for programming", "include": { "aioverview": { "markdown": true }, "paaAioverview": true } } ``` # Extract Google News Source: https://cloro.dev/docs/api-reference/endpoint/monitor-google-news api-reference/openapi.json POST /v1/monitor/google/news Extract structured news articles from Google News including titles, links, snippets, sources, dates, and thumbnails Extract structured news articles from Google News: titles, links, snippets, sources, publication dates, and thumbnail images. ## Request parameters **Required parameters:** * `query` (string): The search query to execute on Google News (1-10,000 characters) * `country` (string): ISO 3166-1 alpha-2 country code (uppercase) for localized news results. Required — there is no default **Optional parameters:** * `device` (string): Device the search is run from. Options: `desktop` (default), `ios`, `android`, and `mobile` (an alias for `android`). `ios` emulates Safari on iPhone and `android` emulates Chrome on Android; all three phone values return the mobile news SERP layout * `pages` (integer): Number of news results pages to scrape (1-10). Defaults to `1` * `include.html` (boolean): Include raw HTML response. Defaults to `false` ## Response schema Includes [common response fields](/docs/guides/making-requests/sync#common-response-fields) plus: ### Google News results | Field | Type | Description | | -------------------- | ----- | --------------------------------------------------------------------------------------------------------------------- | | `result.newsResults` | array | [News articles](/docs/api-reference/endpoint/google-news/news-articles) from Google News | | `result.html` | array | Array of URLs to the full HTML, one per scraped page (if requested, [expires after 24 hours](/docs/guides/output-formats)) | ## Response objects | Section | Description | | ------------------------------------------------------------------ | --------------------------------------------------------------------------- | | [News articles](/docs/api-reference/endpoint/google-news/news-articles) | Article titles, links, snippets, sources, publication dates, and thumbnails | ## Usage examples ### Basic news search ```json theme={null} { "query": "climate change", "country": "US" } ``` ### Multi-page news search ```json theme={null} { "query": "artificial intelligence", "pages": 3, "country": "GB" } ``` Articles from all 3 pages are combined into a single response. ### Mobile news results The mobile news SERP as seen from an iPhone. Use `"device": "android"` for Chrome on Android instead: ```json theme={null} { "query": "technology news", "device": "ios", "country": "US" } ``` ### Include HTML response Raw HTML alongside the structured data: ```json theme={null} { "query": "sports news", "country": "US", "include": { "html": true } } ``` # Extract Grok Source: https://cloro.dev/docs/api-reference/endpoint/monitor-grok api-reference/openapi.json POST /v1/monitor/grok Extract structured data from Grok — answer text and cited sources, including X/Twitter references — for any prompt across supported regions Extract structured data from Grok with real-time web [sources with enhanced metadata](/docs/api-reference/endpoint/grok/sources). Unlike other AI search providers, Grok provides extra contextual information for each source including preview text, creator details, site information, and image URLs. **Web search enabled** This endpoint uses Grok's default interface, which always performs web searches for all requests to provide real-time information with source citations. ## Request parameters Uses [common parameters](/docs/guides/making-requests/sync#common-parameters). **Endpoint-specific options:** * `include.rawResponse` (boolean): Include raw streaming response events. Defaults to `false` ## Response objects The response includes the following sections. See each subpage for the full schema. | Section | Description | | ----------------------------------------------- | ---------------------------------------------------------------------------- | | [Sources](/docs/api-reference/endpoint/grok/sources) | Sources with preview text, creator details, site information, and image URLs | ## Response schema Includes [common response fields](/docs/guides/making-requests/sync#common-response-fields) plus: | Field | Type | Description | | ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------- | | `result.text` | string | Grok's response text | | `result.html` | string | A URL to the full HTML of the response (included when `include.html` is true, [expires after 24 hours](/docs/guides/output-formats)) | | `result.sources` | array | Array of web [sources](/docs/api-reference/endpoint/grok/sources) with enhanced metadata | | `result.searchQueries` | string\[] | Array of search queries Grok used to gather information | | `result.model` | string | The model identifier used to generate the response (e.g., "grok-4-auto") | | `result.markdown` | string | Response formatted in Markdown (included when `include.markdown` is true) | | `result.rawResponse` | array | Array of Grok's streamed response events (included when `include.rawResponse` is true) | # Extract Perplexity Source: https://cloro.dev/docs/api-reference/endpoint/monitor-perplexity api-reference/openapi.json POST /v1/monitor/perplexity Extract structured data from Perplexity AI about your brand, products, or any topic across various regions Extract structured data from Perplexity AI with real-time web sources. Beyond the text response, cloro detects query intent and extracts shopping products, media, travel information, and location data. **Web search enabled** This endpoint uses Perplexity's default interface, which searches the web on every request and returns source citations. ## Request parameters Uses [common parameters](/docs/guides/making-requests/sync#common-parameters). **Endpoint-specific options:** * `include.rawResponse` (boolean): Include raw streaming response events. Defaults to `false` ## Response objects | Section | Description | | ------------------------------------------------------------------------- | ------------------------------------------------ | | [Sources](/docs/api-reference/endpoint/perplexity/sources) | Source citations referenced in the response | | [Shopping cards](/docs/api-reference/endpoint/perplexity/shopping-cards) | Product cards with pricing, ratings, and offers | | [Media](/docs/api-reference/endpoint/perplexity/media) | Videos and images relevant to the query | | [Travel and places](/docs/api-reference/endpoint/perplexity/travel-and-places) | Hotels and places when travel intent is detected | ## Response schema Includes [common response fields](/docs/guides/making-requests/sync#common-response-fields) plus: ### Core response fields | Field | Type | Description | | ----------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------- | | `result.text` | string | Perplexity's response text | | `result.html` | string | A URL to the full HTML of the response (included when `include.html` is true, [expires after 24 hours](/docs/guides/output-formats)) | | `result.sources` | array | Array of [sources](/docs/api-reference/endpoint/perplexity/sources) referenced in the response | | `result.markdown` | string | Response formatted in Markdown (included when `include.markdown` is true) | | `result.rawResponse` | array | Array of Perplexity's streamed response events (included when `include.rawResponse` is true) | | `result.shopping_cards` | array | [Shopping cards](/docs/api-reference/endpoint/perplexity/shopping-cards) when shopping intent detected | | `result.videos` | array | [Videos](/docs/api-reference/endpoint/perplexity/media) relevant to the query | | `result.images` | array | [Images](/docs/api-reference/endpoint/perplexity/media) relevant to the query | | `result.hotels` | array | [Hotels](/docs/api-reference/endpoint/perplexity/travel-and-places) when travel intent detected | | `result.places` | array | [Places](/docs/api-reference/endpoint/perplexity/travel-and-places) when location intent detected | ### Additional response data | Field | Type | Description | | ----------------------------- | ----- | ---------------------------------------------------------------------------------------- | | `result.related_queries` | array | Suggested follow-up search queries | | `result.search_model_queries` | array | Internal search queries (fan-outs) used to generate response, one query string per entry | ## Common questions ### I'm getting 500 errors on certain prompts. What's happening? Perplexity occasionally classifies certain queries as "personal search" (queries with location context, first-person phrasing, or user-specific intent) and returns a 500 error. Mitigations: 1. Remove in-prompt location injections — use the `country` request parameter instead of writing location into the prompt. 2. Append `"This is not a personal search."` to affected prompts (reduces but does not eliminate failures; may slightly affect response quality). 3. Build retry logic: Perplexity already retries internally 5–10 times, so persistent 500s after retries indicate a genuine personal-search classification. ### How common are Perplexity shopping cards? `result.shopping_cards` appear in fewer than 1 in 1,000 Perplexity responses (approximately 0.1% as of May 2026, compared to \~3.5% for ChatGPT and \~11% for Copilot). Do not build a workflow that depends on Perplexity returning shopping data. ### I see `[cite ]` text in the response. Is that a bug? No. Around 1% of Perplexity responses carry `[cite ]` placeholder text in `result.text` and `result.markdown` — an upstream behavior where Perplexity emits a citation marker that doesn't resolve to a URL. Strip or ignore `[cite ]` tokens in post-processing. # Perplexity citation pills schema Source: https://cloro.dev/docs/api-reference/endpoint/perplexity/citation-pills Schema for inline citation pills returned by the Perplexity endpoint, with each cited source attached to the visible pill chip it appears on in the answer. This section documents the **citationPills** data returned by the [Perplexity endpoint](/docs/api-reference/endpoint/monitor-perplexity). Perplexity renders inline bracketed citation markers (e.g. `[1][2][3]`) next to its answer text. The `result.citationPills` array exposes those bracketed citations denormalized, as part of the Perplexity response so no separate API call is needed: each entry is one **(pill, source)** pair carrying a per-source `label` (the source's own page title), a `citationPillId` that groups entries from the same bracketed citation, and the per-source `url`/`domain`/`description`/`position`. For use cases, pricing context, and copy-paste examples, see the [Perplexity citations API](https://cloro.dev/perplexity/sources/) page on the product site. When a bracketed citation references N sources, the array contains N entries sharing the same `citationPillId` but carrying different per-source `label`, `url`, and `domain`. Group by `citationPillId` to recover the citation-level structure. The field is omitted from `result` when the answer has no pills. ## Example request ```json theme={null} { "prompt": "best laptops for programming", "country": "US" } ``` ## Citation pill structure | Field | Type | Description | | ---------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `label` | string | Per-source title from the sources rail (e.g. `"Best Programming Laptops 2026 — TechCrunch"`). Always present; may be an empty string when the rail has no title for this source — read `domain` / `url` for source identity in that case. | | `citationPillId` | integer | 1-based ordinal shared by all entries from the same chip. | | `url` | string | Direct URL of the cited source. | | `domain` | string | Host extracted from `url`, for grouping and display. | | `description` | string | Source snippet from the sources rail when Perplexity ships one. Omitted when absent. | | `position` | integer | 1-based position of this source in the sibling [`result.sources`](/docs/api-reference/endpoint/perplexity/sources) array. | ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are several laptops for programming...", "sources": [ { "position": 1, "url": "https://techcrunch.com/laptops", "label": "Best Programming Laptops 2026 — TechCrunch", "description": "Latest laptop reviews and recommendations" }, { "position": 2, "url": "https://www.youtube.com/watch?v=example", "label": "Best Laptops 2026", "description": "Video review of programming laptops" }, { "position": 3, "url": "https://wired.com/programming-laptops", "label": "Wired Laptop Buyer's Guide", "description": "Programming laptop buyer's guide" } ], "citationPills": [ { "label": "Best Programming Laptops 2026 — TechCrunch", "citationPillId": 1, "url": "https://techcrunch.com/laptops", "domain": "techcrunch.com", "description": "Latest laptop reviews and recommendations", "position": 1 }, { "label": "Wired Laptop Buyer's Guide", "citationPillId": 1, "url": "https://wired.com/programming-laptops", "domain": "wired.com", "description": "Programming laptop buyer's guide", "position": 3 } ] } } ``` # Perplexity media results schema Source: https://cloro.dev/docs/api-reference/endpoint/perplexity/media Schema for videos and images returned by the Perplexity endpoint when media is relevant to the query, including thumbnails, source URLs, and captions. This section documents the **media** data returned by the [Perplexity endpoint](/docs/api-reference/endpoint/monitor-perplexity): videos and images Perplexity surfaces when they are relevant to the query, part of the Perplexity response so no separate API call is needed. Each media type appears in the order Perplexity ranks it; array index `0` is the top result. For use cases, pricing context, and copy-paste examples, see the [Perplexity media API](https://cloro.dev/perplexity/media/) page on the product site. Videos in Perplexity ## Example request Media is surfaced based on query relevance; send a prompt where videos or images are likely to be useful (tutorials, visual subjects, news coverage). No flag is required. ```json theme={null} { "prompt": "how to change a bicycle tire", "country": "US" } ``` ## Media structure | Field | Type | Description | | --------------- | ----- | ------------------------------- | | `result.videos` | array | Video content found in response | | `result.images` | array | Image content found in response | ## Media item structure | Field | Type | Description | | ------------------ | ------ | ------------------------------------------------- | | `title` | string | Media title | | `url` | string | Media URL | | `thumbnail` | string | Thumbnail URL | | `medium` | string | Media type ("video", "image") | | `source` | string | Source platform ("youtube", "stock\_photo", etc.) | | `image_width` | number | Original image width | | `image_height` | number | Original image height | | `thumbnail_width` | number | Thumbnail width | | `thumbnail_height` | number | Thumbnail height | ## Response example ```json theme={null} { "success": true, "result": { "text": "Changing a bicycle tire is straightforward once you know the steps...", "videos": [ { "title": "How to Change a Bike Tire", "url": "https://www.youtube.com/watch?v=example", "thumbnail": "https://i.ytimg.com/vi/example/hqdefault.jpg", "medium": "video", "source": "youtube", "thumbnail_width": 480, "thumbnail_height": 360 } ], "images": [ { "title": "Bicycle tire replacement", "url": "https://example.com/tire.jpg", "thumbnail": "https://example.com/tire-thumb.jpg", "medium": "image", "source": "stock_photo", "image_width": 1920, "image_height": 1080, "thumbnail_width": 320, "thumbnail_height": 180 } ] } } ``` # Perplexity shopping cards schema Source: https://cloro.dev/docs/api-reference/endpoint/perplexity/shopping-cards Schema for shopping product cards returned by the Perplexity endpoint when shopping intent is detected, including titles, prices, ratings, and merchant links. This section documents the **shopping cards** data returned by the [Perplexity endpoint](/docs/api-reference/endpoint/monitor-perplexity): product listings with pricing, ratings, and merchant offers, extracted when Perplexity detects shopping intent and part of the Perplexity response so no separate API call is needed. For use cases, pricing context, and copy-paste examples, see the [Perplexity shopping API](https://cloro.dev/perplexity/shopping/) page on the product site. ## Example request Shopping cards are intent-detected; send a prompt that expresses shopping intent (for example, asking for product recommendations or where to buy). No flag is required. ```json theme={null} { "prompt": "best running shoes under $150", "country": "US" } ``` ## Shopping card structure | Field | Type | Description | | ----------------------- | ----- | -------------------------------------------------------- | | `result.shopping_cards` | array | Shopping product cards with detailed product information | ## Card fields | Field | Type | Description | | ---------- | ----- | ------------------------ | | `products` | array | Array of product objects | | `tags` | array | Optional category tags | ## Product structure | Field | Type | Always present | Description | | ---------------- | ------- | -------------- | ---------------------------------------------------------------------------------------------------- | | `title` | string | Yes | Product name | | `id` | string | Yes | Product identifier | | `url` | string | Yes | Product page URL | | `price` | string | Yes | Current price (e.g., `"$140.00"`) | | `merchant` | string | Yes | Merchant name | | `available` | boolean | Yes | Whether the product is currently available | | `imageUrls` | array | Yes | Product image URLs | | `numReviews` | number | Yes | Number of reviews (`0` when none) | | `offers` | array | Yes | Shopping offers from individual merchants — see [offers](#offer-structure) | | `position` | integer | Sometimes | 1-indexed rank across all products in all shopping cards in the response (flat, not reset per card). | | `rating` | number | Sometimes | Product rating (0-5) | | `original_price` | string | Sometimes | Original price before discount | | `description` | string | Sometimes | Product description | | `variants` | array | Sometimes | Product variants (size, color, etc.) | ## Offer structure Each entry in `offers` represents a single merchant listing for the product: | Field | Type | Always present | Description | | ---------------- | ------- | -------------- | -------------------------------------------------------- | | `url` | string | Yes | Offer URL with Perplexity referral parameters | | `price` | string | Yes | Offer price (e.g., `"$140.00"`) | | `merchant_name` | string | Yes | Merchant name | | `product_name` | string | Yes | Product name as listed by the merchant | | `available` | boolean | Yes | Whether the offer is in stock | | `price_details` | object | Yes | Detailed price breakdown (currently `{ display_price }`) | | `original_price` | string | Sometimes | Original price before discount (`null` when not on sale) | ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are several running shoes under $150...", "shopping_cards": [ { "tags": ["running", "athletic"], "products": [ { "title": "Nike Pegasus 41", "position": 1, "url": "https://www.nike.com/t/pegasus-41", "description": "Responsive cushioning for daily training runs.", "price": "$140.00", "original_price": null, "rating": 4.6, "numReviews": 1284, "imageUrls": [ "https://static.nike.com/pegasus-41.jpg" ], "merchant": "Nike", "available": true, "id": "nike-pegasus-41", "variants": [], "offers": [ { "url": "https://www.nike.com/t/pegasus-41?utm_source=Perplexity&utm_medium=referral", "price": "$140.00", "available": true, "product_name": "Nike Pegasus 41 Running Shoes", "merchant_name": "Nike", "original_price": null, "price_details": { "display_price": "$140.00" } } ] } ] } ] } } ``` # Perplexity sources schema Source: https://cloro.dev/docs/api-reference/endpoint/perplexity/sources Schema for source citations returned by the Perplexity endpoint, listing URLs, titles, snippets, and publishers referenced throughout the generated answer. This section documents the **sources** data returned by the [Perplexity endpoint](/docs/api-reference/endpoint/monitor-perplexity): the citations behind the answer, part of the Perplexity response so no separate API call is needed. They follow the [common sources structure](/docs/guides/making-requests/sync#sources-array-structure) (`position`, `url`, `label`, and `description`) with no Perplexity-specific fields. For use cases, pricing context, and copy-paste examples, see the [Perplexity citations API](https://cloro.dev/perplexity/sources/) page on the product site. Sources in Perplexity ## Example request ```json theme={null} { "prompt": "best laptops for programming", "country": "US" } ``` ## Response example ```json theme={null} { "success": true, "result": { "text": "Here are several laptops for programming...", "sources": [ { "position": 1, "url": "https://example.com/best-laptops-2026", "label": "Best laptops for programmers in 2026", "description": "A guide to the top laptops for software development this year." }, { "position": 2, "url": "https://example.com/dev-laptops-review", "label": "Developer laptop reviews", "description": "Reviews of laptops aimed at software developers." } ] } } ``` # Perplexity travel and places schema Source: https://cloro.dev/docs/api-reference/endpoint/perplexity/travel-and-places Schema for hotel listings and place cards returned by the Perplexity endpoint when travel intent is detected, including pricing, ratings, and amenities. This section documents the **travel and places** data returned by the [Perplexity endpoint](/docs/api-reference/endpoint/monitor-perplexity): when Perplexity detects a travel-intent query it returns structured hotel listings in `result.hotels` and general place data in `result.places`, part of the Perplexity response so no separate API call is needed. Hotels and places appear in the order Perplexity ranks them; array index `0` is the top result for each type. For use cases, pricing context, and copy-paste examples, see the [Perplexity shopping API](https://cloro.dev/perplexity/shopping/) page on the product site. Location data in Perplexity ## Example request Travel and places data is intent-detected; no flag is required. ```json theme={null} { "prompt": "best hotels in Lisbon for families", "country": "PT" } ``` ## Hotel structure | Field | Type | Description | | ------------- | ------ | --------------------------- | | `name` | string | Hotel name | | `url` | string | Hotel page URL | | `rating` | number | Hotel rating (0-5) | | `num_reviews` | number | Number of reviews | | `address` | array | Address lines | | `phone` | string | Phone number | | `description` | string | Hotel description | | `image_url` | string | Main hotel image URL | | `images` | array | Additional hotel image URLs | | `lat` | number | Latitude | | `lng` | number | Longitude | | `price_level` | string | Price level indicator | | `categories` | array | Hotel categories | ## Place structure | Field | Type | Description | | ------------ | ------ | ------------------ | | `name` | string | Place name | | `url` | string | Place page URL | | `address` | array | Address lines | | `rating` | number | Place rating (0-5) | | `lat` | number | Latitude | | `lng` | number | Longitude | | `categories` | array | Place categories | | `map_url` | string | Map URL | | `images` | array | Place image URLs | ## Response example ```json theme={null} { "success": true, "result": { "text": "Lisbon has several family-friendly hotels in central neighborhoods...", "hotels": [ { "name": "Hotel Mundial", "url": "https://www.hotel-mundial.pt", "rating": 4.3, "num_reviews": 6421, "address": ["Praça Martim Moniz 2", "1100-341 Lisboa"], "phone": "+351 21 884 2000", "description": "Central family hotel with rooftop views.", "image_url": "https://example.com/mundial.jpg", "images": [ "https://example.com/mundial-1.jpg", "https://example.com/mundial-2.jpg" ], "lat": 38.7167, "lng": -9.1357, "price_level": "$$", "categories": ["Hotel", "Family-friendly"] } ], "places": [ { "name": "Belem Tower", "url": "https://www.patrimoniocultural.gov.pt/belem-tower", "address": ["Avenida Brasília", "Lisboa"], "rating": 4.6, "lat": 38.6916, "lng": -9.2160, "categories": ["Landmark", "UNESCO site"], "map_url": "https://maps.google.com/?cid=belem-tower", "images": ["https://example.com/belem.jpg"] } ] } } ``` # List of states Source: https://cloro.dev/docs/api-reference/endpoint/states api-reference/openapi.json GET /v1/states Returns a list of US states supported for state-level geo-targeting. Only US is currently supported — other country values return an empty array. Returns the states available for state-level geo-targeting, which is supported for the US only. State targeting works on ChatGPT, Copilot, Perplexity, Gemini, and Grok; Google and AI Mode use the `location` / `uule` parameters for sub-country precision instead. Adding the `state` parameter to a monitor request adds **+2 credits** on top of the base cost and any other add-ons. ## Request parameters | Parameter | Type | Required | Description | Example | | --------- | ------ | -------- | ------------------------------------------------------------- | ------- | | `country` | string | Yes | ISO 3166-1 alpha-2 country code. Only `"US"` returns results. | `US` | ## Example usage ```bash cURL theme={null} curl -X GET "https://api.cloro.dev/v1/states?country=US" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```python Python theme={null} import requests response = requests.get( "https://api.cloro.dev/v1/states", params={"country": "US"}, headers={"Authorization": "Bearer YOUR_API_KEY"} ) states = response.json() # [{"code": "AL", "name": "Alabama"}, {"code": "AK", "name": "Alaska"}, ...] ``` ```javascript Node.js theme={null} const response = await fetch('https://api.cloro.dev/v1/states?country=US', { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }); const states = await response.json(); // [{"code": "AL", "name": "Alabama"}, {"code": "AK", "name": "Alaska"}, ...] ``` ```json Response theme={null} [ { "code": "AL", "name": "Alabama" }, { "code": "AK", "name": "Alaska" }, { "code": "AZ", "name": "Arizona" }, { "code": "CA", "name": "California" }, { "code": "DC", "name": "District of Columbia" }, { "code": "NY", "name": "New York" }, { "code": "TX", "name": "Texas" } ] ``` ## Response schema | Field | Type | Description | | ------ | ------ | ----------------------------------------- | | `code` | string | USPS two-letter state code (e.g., `"CA"`) | | `name` | string | Full state name (e.g., `"California"`) | ## Using states in monitor requests Pass the `code` value as `state` alongside `country: "US"`: ```bash cURL theme={null} curl -X POST "https://api.cloro.dev/v1/monitor/chatgpt" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "best Italian restaurants near me", "country": "US", "state": "CA" }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.cloro.dev/v1/monitor/chatgpt", headers={"Authorization": "Bearer YOUR_API_KEY"}, json={ "prompt": "best Italian restaurants near me", "country": "US", "state": "CA" } ) ``` ## Common questions ### Can I use `state` with a non-US country? It depends on the operation: * **`GET /v1/states?country=GB`** (or any non-US country) — returns an empty array, no error. * **`state` field in a monitor request** (e.g., `POST /v1/monitor/chatgpt`) with a non-US `country` — returns a **400 validation error**. The `state` field is only accepted when `country` is `"US"`. ### How does state targeting work? cloro routes your request through a proxy located in that US state, which changes the local results, regional content, and location-specific information the AI provider returns. # API reference Source: https://cloro.dev/docs/api-reference/introduction Reference for the cloro API: base URL, authentication, response envelope, and monitoring endpoints for ChatGPT, Gemini, Copilot, Perplexity, and Google. The cloro API is a REST interface with predictable, resource-oriented URLs and JSON responses. This reference documents every endpoint, grouped by provider. ## Base URL All requests go to: ``` https://api.cloro.dev ``` ## Authentication Every request is authenticated with a Bearer token: ```bash theme={null} Authorization: Bearer YOUR_API_KEY ``` Get your key from the [dashboard](https://dashboard.cloro.dev). See [Authentication](/docs/guides/authentication) for details. ## Response shape Monitor endpoints return a `success` flag and a `result` object: ```json theme={null} { "success": true, "result": { "text": "...", "sources": [] } } ``` See [Request & response](/docs/guides/making-requests) for the full request and response structure, [Rate & concurrency limits](/docs/guides/concurrency) for headers, and [Providers](/docs/guides/providers) for per-endpoint credit costs. ## Endpoints Browse endpoints by provider — ChatGPT, Google, Perplexity, Copilot, AI Mode, Grok, Gemini, and Google News — plus utility endpoints for country and state lists and async task management. # Changelog Source: https://cloro.dev/docs/changelog Stay up to date with the latest cloro API product updates, new features, enhancements, bug fixes, and announcements about our monitoring endpoints. Get live updates of our changelog on your [Slack](https://slack.com/help/articles/218688467-Add-RSS-feeds-to-Slack) with our RSS feed. **Request latency is now returned in responses** Every API response carries an [`X-Latency-Ms`](/docs/guides/concurrency#latency-header) header with the milliseconds the API spent on that request. It excludes network transit. Async task processing time is reported separately as [`task.latencyMs`](/docs/guides/making-requests/async#step-2-receive-the-results), measured from first pickup to the final outcome and excluding the initial queue wait. It is returned by [`GET /v1/async/task/{taskId}`](/docs/api-reference/endpoint/get-task-status), in the [webhook payload](/docs/guides/webhooks#receiving-deliveries), and in create responses, where it is `null` until the task finishes. Both additions are additive — no existing field or header changed. **Incident: `result.html` unavailable between 12:00 and 12:47 UTC** A DNS misconfiguration meant the URLs returned in `result.html` did not resolve between **12:00 and 12:47 UTC on 11 August 2026**. Requests completed and were charged as normal, and every other `result` field was unaffected. DNS has been corrected. The HTML from that window cannot be recovered. Contact support with the number of affected credits and we will reimburse them. **Google Search and Google News: `device` now accepts `ios` and `android`** The `device` parameter on the [Google Search](/docs/api-reference/endpoint/monitor-google) and [Google News](/docs/api-reference/endpoint/monitor-google-news) endpoints takes two new values: `ios`, which runs the search as Safari on an iPhone, and `android`, which runs it as Chrome on Android. Both return the mobile SERP layout, so the response shape is unchanged and there is no extra credit cost. `mobile` still works and is now an alias for `android`, so existing requests behave exactly as before. Use `ios` when you need to see what Google serves iPhone users specifically. **Merchant offers on Google Search and AI Mode: `result.productResults`** Both Google surfaces can now return the merchant offers behind a product — direct merchant URL, price, installment terms, and stock, delivery and returns badges — as a new `productResults` array, in the same shape on both. On [Google Search](/docs/api-reference/endpoint/google/product-results) it appears whenever Google's right-rail product panel does, with no flag and no extra credits; [`shoppingCards`](/docs/api-reference/endpoint/google/shopping-cards) is unchanged. On [Google AI Mode](/docs/api-reference/endpoint/aimode/product-results) it is opt-in via `include.expandProducts` and costs **+1 credit per product cluster returned**, capped at +6. **MCP server** cloro now ships a hosted [MCP server](/docs/integrations/mcp) that exposes the monitor endpoints as agent tools for any MCP client — Claude, Claude Code, Cursor, or your own agent. Connect over Streamable HTTP at `https://mcp.cloro.dev/YOUR_CLORO_API_KEY/mcp` and ask your agent how AI assistants answer a prompt, whether a brand appears in Google's AI Overview, or what Google News is saying — no HTTP calls to hand-write. The server forwards your own API key to the API, so billing, rate limits, and concurrency work exactly as they do for direct requests. See the [MCP server guide](/docs/integrations/mcp) for client configuration and the full tool list. **New endpoint: read your credit balance with `GET /v1/credits`** The new [`GET /v1/credits`](/docs/api-reference/endpoint/get-credits) endpoint returns your current balance (`remaining`), credits per cycle (`perCycle`), and the cycle reset date (`cycleResetsAt`). Async-only workloads can now read their balance and reset date programmatically — for low-balance alerts or a submission breaker — without making a billable sync `/v1/monitor/*` call just to inspect the `X-Credits-Remaining` header. **Google AI Mode: 33 newly-supported countries** The [Google AI Mode endpoint](/docs/api-reference/endpoint/monitor-aimode) now accepts 33 additional countries and territories that Google has rolled out since the last update, bringing its supported list in line with the [official AI Mode availability list](https://support.google.com/websearch/answer/16011537#zippy=%2Csupported-countries-territories). Every country accepted by AI Mode is also accepted by the [Google Search endpoint](/docs/api-reference/endpoint/monitor-google), so you can now freely use the same `country` value across both. The additions are: `BL`, `BV`, `BY`, `EH`, `ER`, `ET`, `GF`, `GP`, `GU`, `GW`, `IS`, `KM`, `KZ`, `LK`, `LR`, `MC`, `MF`, `MO`, `MQ`, `MR`, `NC`, `PF`, `PM`, `RE`, `SY`, `SZ`, `TF`, `TZ`, `VA`, `WF`, `XK`, `YE`, `YT`. The [`/v1/countries`](/docs/api-reference/endpoint/countries) endpoint now lists them under `model=aimode`. **Google AI Mode and AI Overview: France (`FR`) is now supported** You can now send `country: "FR"` to the [Google AI Mode endpoint](/docs/api-reference/endpoint/monitor-aimode) and use `include.aioverview` on the [Google endpoint](/docs/api-reference/endpoint/monitor-google) for `FR` requests. Previously these were rejected as unsupported regions; regular Google Search already accepted `FR`. The [`/v1/countries`](/docs/api-reference/endpoint/countries) endpoint now lists `FR` under `model=aimode` and `model=aioverview`. **ChatGPT: new mobile-web response format** OpenAI now serves a share of logged-out ChatGPT traffic through a new mobile-web format that streams the answer as HTML fragments rather than the usual event stream. On responses served this way: * `rawResponse` (when you opt in) has a different, HTML-fragment shape alongside the existing event-stream shape. * `result.model`, `result.searchQueries`, and `result.mapSearchQueries` come back **empty** — the mobile format carries none of the stream metadata these are parsed from. * `result.sources` is **shorter**; the format exposes fewer full source entries. * The answer `text` / `markdown` and inline `citationPills` are **unaffected**. Which format OpenAI serves is decided on their side, per request. cloro replicates a real user, so it does not — and by design should not — force a particular interface; a share of responses will therefore arrive in the mobile-web format. Fields that were previously always populated can now be empty on mobile-web responses. Treat `result.model`, `result.searchQueries`, and `result.mapSearchQueries` as possibly empty, and expect fewer entries in `result.sources`. If you consume `rawResponse`, handle the HTML-fragment shape in addition to the event-stream shape. **ChatGPT: `visibility`, `adsResponseIndex`, and `rendered` on ads** Each ad in `result.ads` (set `include.ads: true`) now carries three fields that separate what OpenAI **served** from what the user actually **saw**. OpenAI serves candidate ads but the interface renders at most one; previously `result.ads` listed the served candidates with no way to tell which was shown. `rendered` is `true` only for the ad displayed as a visible card, so you can count real impressions instead of the array length. `visibility` exposes OpenAI's serving status (`allowed`, or `hidden` when an ad was filtered), and `adsResponseIndex` gives the ad's served slot position. The fields ride the existing `include.ads` flag, so there is no new parameter and no extra credit cost. See the [ChatGPT ads schema](/docs/api-reference/endpoint/chatgpt/ads#served-vs-shown). **New `DELETE /v1/async/queue` endpoint to clear your async queue** You can now clear your organization's pending async queue in a single call with the new [Clear queue endpoint](/docs/api-reference/endpoint/clear-async-queue). It deletes every task still in the `QUEUED` state and returns the number removed (`{ "success": true, "cleared": }`), giving you a self-serve way to drain a backlog instead of waiting for it to process. Tasks already `PROCESSING` are in-flight and left running, and `COMPLETED`/`FAILED` tasks are untouched. Queued tasks aren't charged until they're processed, so clearing them has no effect on your credit balance, and the call is idempotent — an empty queue simply returns `cleared: 0`. Check how many tasks are queued first with the [async status endpoint](/docs/api-reference/endpoint/get-async-status). **Perplexity: `search_model_queries` is now an array of strings** Around **15th July 2026, Perplexity changed the structure of its underlying response object** — it moved the model's search queries into a new location and stopped sending the per-query engine and result-limit metadata. During the interim, that upstream change had left `result.search_model_queries` empty on most responses. This update adapts to the new structure and restores the field. The [Perplexity endpoint](/docs/api-reference/endpoint/monitor-perplexity) now returns `result.search_model_queries` as a plain array of query strings, matching how ChatGPT exposes the same fan-out concept. Each entry was previously an object with `query`, `engine`, and `limit` fields; `engine` was always `"web"` and `limit` was always `8`, stale constants that no longer corresponded to anything Perplexity sends. We've dropped them and kept only the query text. This is a breaking change to the shape of `search_model_queries` entries. If you read `.query` off each entry, switch to reading the string directly (`result.search_model_queries[i]` instead of `result.search_model_queries[i].query`); if you depended on `.engine` or `.limit`, those values were fixed constants and are no longer emitted. **ChatGPT: new `mapSearchQueries` field** The [ChatGPT endpoint](/docs/api-reference/endpoint/monitor-chatgpt) now exposes a `mapSearchQueries` array on `result` when you set `include.searchQueries: true`. When ChatGPT renders a map block for a local-intent prompt (e.g. "best coffee shops in San Francisco", "plumbers near me"), it fires a separate query at its maps tool to gather the businesses behind `result.map`. That query previously lived only in the raw stream. It now has a structured home, distinct from `result.searchQueries` (the web-search fan-out), which the two can diverge on for the same prompt. The field rides the existing `include.searchQueries` flag, so there is no new parameter and no extra credit cost. It is additive and omitted when no map appears in the response. See the [ChatGPT response schema](/docs/api-reference/endpoint/monitor-chatgpt#response-schema). **Google: new `localResults` (local pack / "3-pack") field** The [Google Search endpoint](/docs/api-reference/endpoint/monitor-google) now exposes a `localResults` array on `result` for local-intent queries (e.g. "best pizza in new york city", "plumbers in houston"). Each entry is a place from Google's map-backed local pack, carrying `position` and `title` plus, when Google renders them, `placeId`, `rating`, `reviews`, `price`, `type`, `yearsInBusiness`, `address`, `phone`, `hours`, a `description` snippet, and a `links` object with the place's `website` and `directions` URLs (typical on business/service packs). When the pack shows a "More places" / "More businesses" control, `result.localResultsMoreLink` carries the URL of Google's expanded Local Finder for the query. The field is additive and omitted when no local pack appears on the SERP; extraction is desktop only. See the [Local Pack schema](/docs/api-reference/endpoint/google/local-pack). **Google AI Overview: new `relatedLinks` field** Google AI Overview responses now expose a `relatedLinks` array on `result.aioverview`. These are a citation chip's "View related links" flyout items — URLs Google groups under a pill that are **not** in the `sources` rail (for example a Google Shopping comparison link). Previously these rendered inline in `markdown` with no structured home, which made `markdown` appear to carry more URLs than `sources` / `citationPills`. Each related link now shares the `citationPillId` of its chip (so you can group it with that chip's citation pills) and carries `label`, `url`, and `domain`. The field is additive and omitted when no chip carries related links. See the [AI Overview schema](/docs/api-reference/endpoint/google/ai-overview#related-links). **Copilot fully back, with Brazil (BR) and Italy (IT) re-enabled** The [`/v1/monitor/copilot`](/docs/api-reference/endpoint/monitor-copilot) endpoint is fully operational again. As part of the same rollout, `country: "BR"` and `country: "IT"` are now accepted — requests previously rejected with an unsupported-country error will route through normally. **OpenClaw integration** cloro now ships a plugin for [OpenClaw](https://openclaw.ai) that exposes the monitor endpoints as native agent tools — `cloro_chatgpt`, `cloro_perplexity`, `cloro_gemini`, `cloro_copilot`, `cloro_aimode`, `cloro_google`, and `cloro_google_news`. Ask your OpenClaw agent how AI assistants answer a prompt, whether a brand appears in Google's AI Overview, or what Google News says — no HTTP calls to hand-write. See the [OpenClaw integration guide](/docs/integrations/openclaw) for installation and configuration, including an MCP-server alternative. **Czech Republic (CZ) targeting disabled in ChatGPT** We've removed `country: "CZ"` support for the ChatGPT endpoint. ChatGPT no longer allows anonymous access from Czech IPs — every request is met with a login wall, which scrapers cannot pass through. Requests with `country: "CZ"` to `/v1/chatgpt` will now return an unsupported-country error instead of silently failing. If you target CZ on other scrapers, double-check the results you're getting back. Some providers fall back to proxies in neighboring countries when CZ-resident proxies are unavailable, which can skew localized answers. **Google Search and AI Mode now pin to the requested country by default** Requests to [`/v1/monitor/google`](/docs/api-reference/endpoint/monitor-google) and [`/v1/monitor/aimode`](/docs/api-reference/endpoint/monitor-aimode) that pass `country` but omit both `location` and `uule` now auto-default `location` to the country's canonical name (e.g. `"Portugal"` for `country: "PT"`). Previously, with no UULE set, Google geolocated by the proxy's exit IP — so identical sequential requests could swing between different result locations as each scrape leased a different proxy session. No request changes are needed. Existing calls will see more stable, country-pinned results. Defaulting is country-level only — cloro never auto-picks a city. If you need city- or region-level precision, keep passing `location` (Google canonical name) or `uule` (pre-encoded UULE) explicitly. Unknown country codes leave UULE unset, preserving prior behavior. ```json theme={null} { "query": "best restaurants", "country": "PT" } ``` **Google Knowledge Graph panel now extracted** The Google Search endpoint now returns a `knowledgeGraph` object when a Knowledge Graph panel appears on the SERP. The panel covers people, places, organizations, films, local businesses, and other well-known entities — surfacing structured data like entity type, description, attributes, profiles, ratings, and panel-type-specific fields (streaming options, admission prices, social posts, and more). No request changes needed — the field is included automatically when a panel is present, and omitted from `result` when it is not. ```json theme={null} { "result": { "knowledgeGraph": { "title": "Eminem", "type": "American rapper", "description": "Marshall Bruce Mathers III, known professionally as Eminem...", "website": "https://www.eminem.com", "attributes": [ { "key": "Born", "value": "October 17, 1972" }, { "key": "Genre", "value": "Hip hop" } ] } } } ``` See the [Knowledge Graph reference](/docs/api-reference/endpoint/google/knowledge-graph) for the full schema. **State-level geo-targeting for AI endpoints** You can now target a specific US state when querying ChatGPT, Copilot, Perplexity, Gemini, or Grok. Pass the USPS two-letter code as the `state` parameter alongside `country: "US"` and cloro routes the request through a proxy in that state — giving you localized results at the sub-country level. ```json theme={null} { "prompt": "best electricians near me", "country": "US", "state": "TX" } ``` State targeting costs **+2 credits** on top of the base cost and any other add-ons. Use [`GET /v1/states?country=US`](/docs/api-reference/endpoint/states) to retrieve the full list of supported state codes. Google Search and AI Mode are not affected — those endpoints continue to use `location` / `uule` for sub-country targeting. **Rate limits are now per endpoint** The API rate limit of 1,000 requests per second now applies independently to each endpoint (`/v1/*`) per API key. Previously, all endpoints shared a single bucket, meaning a burst on one endpoint could exhaust the limit for others. Each endpoint now has its own independent bucket, so high-traffic calls to one endpoint no longer affect the rate limit for any other. No action required — existing integrations are unaffected. The `X-RateLimit-Limit` and `X-RateLimit-Remaining` response headers continue to reflect the limit and remaining requests for the specific endpoint called. **Ask the docs — AI assistant now available** You can now ask questions about cloro directly from the docs. The assistant is trained on all published documentation and answers in natural language, with citations linking back to the relevant pages. Look for the **Ask Assistant** button in the navigation bar or the search bar. It works across all pages — API reference, guides, and the changelog. **Webhook signing — verify deliveries came from cloro** Enable webhook signing from the [dashboard](https://dashboard.cloro.dev/webhooks) and every async-task delivery will carry an `X-Cloro-Signature` header (HMAC-SHA256 over `.`) plus `X-Cloro-Timestamp` and `X-Cloro-Webhook-Id`. Your endpoint can confirm the payload's origin and integrity, and reject anything signed more than a few minutes ago to protect against replay. The new [Webhooks guide](/docs/guides/webhooks) is the single home for everything that happens at your endpoint — payload shape, retry behavior, signature verification (with copy-pasteable Node.js, Python, and Go examples), and troubleshooting. Opt-in per organization. **Copilot now returns `searchQueries` (query fan-out)** [`/v1/monitor/copilot`](/docs/api-reference/endpoint/monitor-copilot) responses now include `result.searchQueries` — the web search queries Copilot ran while generating its answer. Always-on, no `include` flag, no surcharge. ```json theme={null} "searchQueries": [ "best laptops for programming 2026", "developer laptop reviews" ] ``` **Structured `citationPills` now on Perplexity, Copilot, and Gemini** All three endpoints now emit an additive `citationPills` array alongside the answer text and sources. Combined with the May 21st rollout to Google AI Overview and AI Mode, cross-provider iteration over AI citation pills now works the same way on six endpoints (ChatGPT, Google AI Overview, AI Mode, Perplexity, Copilot, Gemini). Each entry is one (pill, source) pair carrying a per-source `label` (the source's own page title), a `citationPillId` that groups entries from the same chip, and per-source `url`/`domain`/`description`/`position`. ```json theme={null} "citationPills": [ { "label": "Best Programming Laptops 2026 — TechCrunch", "citationPillId": 1, "url": "https://techcrunch.com/laptops", "domain": "techcrunch.com", "description": "Latest laptop reviews and recommendations", "position": 1 }, { "label": "Wired Laptop Buyer's Guide", "citationPillId": 1, "url": "https://wired.com/programming-laptops", "domain": "wired.com", "description": "Programming laptop buyer's guide", "position": 3 } ] ``` All six providers share a common base shape (`label`, `citationPillId`, `url`, `domain`, `description?`, `position`). ChatGPT carries two additional fields (`datePublished?`, `type`) that the other surfaces do not emit. Consumers writing cross-provider citation-pill iteration should treat `datePublished` and `type` as ChatGPT-only, and use `citationPillId` for chip grouping. Additive change — existing consumers are unaffected. The field is omitted from `result` when the answer carries no pills. Learn more in the per-endpoint citation pills documentation: [Perplexity](/docs/api-reference/endpoint/perplexity/citation-pills), [Copilot](/docs/api-reference/endpoint/copilot/citation-pills), [Gemini](/docs/api-reference/endpoint/gemini/citation-pills). **Structured `citationPills` array on Google AI Overview and AI Mode** Both Google AI surfaces now emit an additive `citationPills` array alongside the answer text and sources. Each entry is one (pill, source) pair carrying a per-source `label` (the source's own page title from the sources rail), a `citationPillId` that groups entries from the same chip, and per-source `url`/`domain`/`description`/`position`. Cross-provider iteration over AI surface citation pills now uses one shape. ```json theme={null} "citationPills": [ { "label": "Best laptops for programmers in 2026", "citationPillId": 1, "url": "https://example.com/best-laptops-2026", "domain": "example.com", "description": "A guide to the top laptops for software development this year.", "position": 1 }, { "label": "MacBook Pro for developers", "citationPillId": 1, "url": "https://example.com/macbook-pro-review", "domain": "example.com", "description": "Why the MacBook Pro is a popular pick for developers.", "position": 3 } ] ``` When a pill cites N sources, the array contains N entries sharing the same `citationPillId` but carrying distinct per-source `label`, `url`, and `domain`. Group by `citationPillId` to recover the pill-level structure. Markdown rendering is unchanged — inline `[anchor](url)` links remain the canonical in-text citation form. The `citationPills` array gives type-aware consumers per-source access without re-parsing markdown. Additive change — existing consumers are unaffected. Learn more in the [AI Overview citation pills](/docs/api-reference/endpoint/google/ai-overview#citation-pills) and [AI Mode citation pills](/docs/api-reference/endpoint/aimode/citation-pills) documentation. **AI Overview ads now carry a `type` discriminator** Every entry in `aioverview.ads[]` on the Google Search endpoint now carries an explicit `type` field. Mirrors the same convention already in place for top-level `ads[]` (added 13th May), so callers can route ads to per-sub-type rendering without re-implementing the price/store presence heuristic. * **`type: "TEXT"`** — text/lead-gen ads. Carry `domain` + `description` (+ shared `position` / `title` / `url` / `image`). * **`type: "SHOPPING"`** — shopping/product ads. Carry `price` + `store` (+ optional `old_price`, + shared fields). Each ad is exactly one sub-type — the two sub-schemas are disjoint. Sub-type-exclusive fields are only present when they apply to that ad, matching what the Google SERP actually renders. `image` is the one field that appears in either sub-type (product photo on shopping cards, hero image on text ads), so consumers should treat it as optional regardless of `type`. ```json theme={null} "ads": [ { "position": 1, "type": "TEXT", "title": "...", "url": "...", "domain": "dell.com", "description": "..." }, { "position": 2, "type": "SHOPPING", "title": "...", "url": "...", "price": { "value": 1499, "currency": "$" }, "store": "Best Buy" } ] ``` Additive change — existing consumers ignoring the new field are unaffected. No opt-in, no extra credit charge. Learn more in the [AI Overview ads documentation](/docs/api-reference/endpoint/google/ai-overview#ads). **AI Overview and AI Mode markdown citations now match ChatGPT** When you opt into markdown output, in-text citations from Google AI Overview and Google AI Mode are now rendered as **standard inline `[anchor](url)` links** in the `markdown` field — the same shape ChatGPT already uses. ```text theme={null} ... the iPhone 17 is the best choice for most people.[CNET](https://www.cnet.com/tech/mobile/best-iphone/)[The best iPhone to buy!!](https://www.youtube.com/shorts/z1aZ3CYcaRE) ``` Anchor text resolves via a deterministic fallback chain: matched source title → Google's pill-chip label → URL host. Multi-source pills (Google's `+N` chips) expand to N adjacent stacked links at the citation point. `result.markdown` is now fully self-contained — no parallel index resolution needed to render citations. Learn more in the [AI Overview](/docs/api-reference/endpoint/google/ai-overview#citation-pills) and [AI Mode](/docs/api-reference/endpoint/aimode/sources) documentation. **AI Overview videos now carry full metadata** `aioverview.videos[]` entries on the Google Search endpoint now populate `title`, `source`, `platform`, `date`, `thumbnail`, and `duration` whenever Google renders them. Previous responses returned only `url` for the vast majority of video cards because the extractor was matching stale Google class names. `url` remains the only guaranteed field. `title`, `source`, `platform`, and `date` are present on roughly 98% of videos; `thumbnail` (\~60%) and `duration` (\~15%) appear only when Google renders the rich carousel preview. Check for field presence before reading. No schema change, no opt-in, no extra credit charge — existing requests start receiving the richer payload automatically. Learn more in the [AI Overview documentation](/docs/api-reference/endpoint/google/ai-overview#videos). **Google Search now returns sponsored shopping-card ads** The Google Search endpoint now extracts every sponsored shopping surface Google renders on the SERP, alongside the existing text ads. Each entry in `ads[]` carries a new `type` discriminator: * **`type: "RESULT"`** — classic text ads at the top or bottom of the page (unchanged). * **`type: "SHOPPING_CARD"`** — shopping-style sponsored cards from the right-hand-side carousel (`blockPosition: "rhs"`) and the top-of-page carousels (`blockPosition: "top"`). Categories observed include `"Sponsored products"`, `"Sponsored vehicles"`, and `"Sponsored hotels"`. `SHOPPING_CARD` ads include `category` (the carousel header label), `price`, optional `oldPrice` (MSRP / sale), `store`, and `imageUrl`. For shopping-card ads, the existing `description` field carries category-specific subtitle fragments joined with `·` (e.g. `Used - 94k miles · Greeley` on a vehicle card). The `ads[]` `blockPosition` enum widens to `"top" | "bottom" | "middle" | "rhs"`. The new fields are emitted whenever the SERP renders the corresponding surface — no opt-in, no additional credit charge. In the organic `shoppingCards[]` field, the `product_link` and `old_price` keys are now emitted as camelCase `productLink` and `oldPrice` to match the rest of the Google response. Learn more in the [sponsored ads documentation](/docs/api-reference/endpoint/google/sponsored-ads). **Google Search and Google News multi-page pricing is now linear** Multi-page requests are now billed at **+2 credits per additional page** instead of the previous tiered structure. Single-page requests are unchanged. | Request | Old cost | New cost | | ---------------------- | --------- | --------------------- | | 1 page | 3 credits | 3 credits (unchanged) | | 1 page + AI Overview | 5 credits | 5 credits (unchanged) | | 3 pages | 4 credits | 7 credits | | 3 pages + AI Overview | 6 credits | 9 credits | | 10 pages | 5 credits | 21 credits | | 10 pages + AI Overview | 7 credits | 23 credits | The same per-page rate applies to [Google News](/docs/api-reference/endpoint/monitor-google-news). See [providers](/docs/guides/providers#google-search-multi-page-pricing) for the full pricing breakdown. **Why the change.** Pages 2-10 are materially harder to serve than page 1: the failure rate climbs as Google ramps up scrutiny on deep pagination. In our infrastructure, fetching pages 1-10 of a single query costs roughly **6× more** than fetching 10× page 1 alone — the per-page cost scales near-exponentially with depth. The old tiered pricing absorbed that cost and under-charged deep-pagination requests; the new per-page rate aligns billing with the actual cost of serving each page and keeps single-page Google requests — the dominant use case — unchanged. **Grok pricing increases to 4 credits per request** Grok's anti-bot defenses have grown materially more complex over recent weeks, raising the proxy, fingerprinting, and retry cost of every successful extraction. To keep base pricing aligned with the underlying infrastructure cost, the [Grok endpoint](/docs/api-reference/endpoint/monitor-grok) now costs **4 credits per request** (previously 3). All other provider prices, addons, and the sync surcharge are unchanged. See the full [providers pricing table](/docs/guides/providers) for context. **Google Search now returns shopping cards and "people are saying" cards** The Google Search endpoint now extracts two additional SERP modules: * **Shopping cards**: product cards from the organic shopping grids ("Popular products" and "More products") in a new optional `shoppingCards` array. Learn more in the [shopping cards documentation](/docs/api-reference/endpoint/google/shopping-cards). * **People are saying**: community-thread cards from Google's "What people are saying" / "Trending posts and discussions" module in a new optional `peopleAreSaying` array. Learn more in the [people are saying documentation](/docs/api-reference/endpoint/google/people-are-saying). Both fields are omitted from `result` when their corresponding module is absent — treat them as optional. No additional credit charge. **Gemini sources no longer include `confidence_level`** Google Gemini stopped returning a confidence score on source citations. The `confidence_level` field has been removed from the [Gemini endpoint](/docs/api-reference/endpoint/monitor-gemini) response schema. All other source fields (`position`, `url`, `label`, `description`) are unchanged. **More Google AI Mode fields** The AI Mode endpoint now returns more structured data: * **Map**: GPS-enriched location results with `gps_coordinates` (latitude/longitude), `type`, `thumbnail`, and a position `index` * **Places**: Inline place cards with `rating`, `reviews`, `type`, `price_level`, `address`, and `status` * **Shopping cards**: Shopping cards now include `old_price` for discount comparison, `snippet` for product descriptions, and `snippet_links` for related links within snippets * **Ads**: Sponsored ad sections are now parsed into a structured `ads` object with individual ad details including title, URL, position, price, store, rating, and reviews * **Inline products**: Product cards embedded within the AI response text are now extracted as `inline_products`, separate from shopping card carousels **Breaking change**: The `price` and `description` fields have been removed from the places object. The `link` field now points to Google's viewer URL format instead of the previous search URL format. All new data is included at no additional credit charge. Learn more in the [AI Mode endpoint documentation](/docs/api-reference/endpoint/monitor-aimode). **Inline products now return offers correctly for ChatGPT** We fixed a bug where ChatGPT inline products were missing pricing and merchant data. Inline products now include the full offer payload (current price, multi-merchant offers from retailers like Amazon and John Lewis, product images, and aggregated ratings) fetched from ChatGPT's product update API. Alongside this fix, shopping cards and inline products move to an opt-in model on the ChatGPT endpoint. Set `include.shopping: true` to include them in your response: * **Pricing and offers**: Current price and multi-merchant offers with availability * **Product images**: Image URLs for product display * **Ratings**: Aggregated rating scores and review counts **Opt-in with additional credit cost** Shopping cards and inline products are disabled by default. Set `include.shopping: true` to enable them. Enabling `shopping`, `rawResponse`, `searchQueries`, or `ads` (or any combination) adds +2 credits to the base cost. The credit surcharge reflects increased extraction complexity. OpenAI now serves product pricing, offers, and merchant data through a separate endpoint, so cloro performs additional fetches per product to enrich the response. **Migration deadline: 8th May 2026** During the migration window, ChatGPT responses still return `shoppingCards` and `inlineProducts` by default. **Starting 8th May 2026**, requests without `include.shopping: true` will no longer receive these fields. Update your integrations before that date to avoid silent data loss. **Breaking change**: Shopping cards and inline products were previously included automatically in ChatGPT responses. They now require `include.shopping: true` in your request. The `details` object on inline products (rationale and themed reviews) is no longer returned. Learn more in our [ChatGPT shopping cards documentation](/docs/api-reference/endpoint/chatgpt/shopping-cards) and [inline products documentation](/docs/api-reference/endpoint/chatgpt/inline-products). **Hyperlocal-targeting for AI Mode with UULE** The AI Mode endpoint now supports two parameters for precise geo-targeting of results: * **`location`**: City-level targeting using [Google canonical location names](https://developers.google.com/google-ads/api/reference/data/geotargets) (e.g., `New York,New York,United States`). Choose from \~100,000 supported locations worldwide * **`uule`**: Advanced targeting using pre-encoded Google UULE strings for users who generate their own UULE values Both parameters work alongside `country` for precise localization. They are mutually exclusive: provide one or the other, not both. No additional credits required. Learn more in our [AI Mode documentation](/docs/api-reference/endpoint/monitor-aimode#usage-examples). **Copilot pricing update** The Copilot endpoint base cost has been updated to 5 credits per request, to reflect the added complexity to ensure sources in 95%+ of cases. **Sync request surcharge** All sync monitor requests (`/v1/monitor/*`) now include a **+2 credit surcharge** on top of the base cost and any feature add-ons. This reflects the higher operational cost of real-time synchronous requests. Async and batch requests (`/v1/async/*`) are **not** affected by this surcharge. Existing customers who used sync endpoints in the 30 days before this change have been granted a grace period. If you have questions about your account, contact [info@cloro.dev](mailto:info@cloro.dev). Learn more in our [provider pricing documentation](/docs/guides/providers#sync-request-surcharge). **Geo-targeting for Google Search** The Google Search endpoint now supports two parameters for precise geo-targeting of search results: * **`location`**: City-level targeting using [Google canonical location names](https://developers.google.com/google-ads/api/reference/data/geotargets) (e.g., `New York,New York,United States`). Choose from \~100,000 supported locations worldwide * **`uule`**: Advanced targeting using pre-encoded Google UULE strings for users who generate their own UULE values Both parameters work alongside `country` for precise localization. They are mutually exclusive -- provide one or the other, not both. No additional credits required. Learn more in our [Google Search documentation](/docs/api-reference/endpoint/monitor-google#uule-geo-targeting). **Sponsored ad extraction for AI Overview** Google AI Overview responses now include structured sponsored ad data when Google injects advertising inside the AI Overview. Ads are exposed as a new `ads` array alongside the existing `text`, `sources`, and `videos` fields. Each ad includes: * **Ad details**: Title, destination URL, domain, and description * **Position tracking**: Position index within the AI Overview ad block Ads also remain visible in `text` and `markdown` output for full transparency. No additional parameters required -- ads are automatically included when present. Learn more in our [AI Overview documentation](/docs/api-reference/endpoint/google/ai-overview). **Map entries for Copilot** The Copilot endpoint now automatically extracts business and place data when Copilot returns local entity information. Map entries include location coordinates, reviews, photos, open/closed status, and Google Place IDs. No additional parameters required. **Features**: * **Automatic extraction**: Map entries are included by default when available, just like shopping cards * **Native structure**: Preserves Copilot's entity format with nested `location`, `reviews`, and `photos` objects * **Google Places integration**: Each entry includes a `placeId` for direct Google Maps lookups * **Live status**: `openState` field shows current business hours (e.g. "Open · Closes 9 PM") * **No extra cost**: Map entries are included at no additional credit charge Learn more in the [Copilot endpoint documentation](/docs/api-reference/endpoint/copilot/map-entries). **Batch task creation** You can now submit up to 500 async tasks in a single API request using the new batch endpoint. This reduces HTTP overhead for high-volume workloads and simplifies bulk monitoring workflows. **Features**: * **Up to 500 tasks per request**: Submit tasks to any combination of providers in one call * **Partial success**: Each task is validated independently. One invalid task doesn't block the rest * **Per-task error reporting**: Failed tasks include detailed error codes (`VALIDATION_ERROR`, `RESOURCE_ALREADY_EXISTS`, `INSUFFICIENT_CREDITS`) with field-level details * **Per-task credits**: Each task's result includes its own credit information * **Full feature support**: Each task in a batch supports priority, idempotency keys, and webhooks Learn more in the [batch task creation documentation](/docs/api-reference/endpoint/create-batch-tasks). **Priority support for async tasks** You can now assign a priority to async tasks so that time-sensitive work is processed first. Set the `priority` field (1-10) when creating a task. Higher numbers mean higher urgency. **Features**: * **Priority range**: Integer from 1 (lowest, default) to 10 (highest) * **Smart ordering**: Higher-priority tasks are processed before lower-priority ones within your queue. Same-priority tasks are processed in FIFO order * **Priority breakdown**: The [async status endpoint](/docs/api-reference/endpoint/get-async-status) now shows queued task counts per priority level * **Backward compatible**: Tasks without a priority default to 1, so existing integrations are unaffected Learn more in our [async requests documentation](/docs/guides/making-requests/async#request-prioritization). **More business data in ChatGPT map entries** ChatGPT map entries now return mapped business data from providers (Yelp, Google Business) with 47 structured fields, replacing the previous 10-field structure. What changed: * **Mapped data**: Map entries now include all available business fields from ChatGPT's providers (Yelp, Google Business, Yelp-feed) * **47 mapped fields**: Access to detailed location data, operating hours, attributes, reviews, images, and provider-specific metadata * **Structured schema**: Properly typed fields with consistent camelCase naming conventions * **Field name updates**: `phone_number` → `phone`, `country` → `countryCode`, `review_count` → `reviewCount` * **Null-safe**: All fields except `name` and `position` are optional Key improvements: * **More location data**: Full address breakdown with coordinates (`latitude`/`longitude`), distance from user (`distanceMeters`), and human-readable location strings * **Hours**: Weekly schedule with next opening times (`nextOpenHour`) and special hours (`specialHours`) * **Attributes**: Provider-specific details like parking options, WiFi availability, accepted payment methods, and accessibility features * **Images**: Multiple image URLs (`imageUrls`) with provider-hosted images (`providerImages`) * **Metadata**: Business claimed status (`isClaimed`), opening/closing dates (`dateOpened`/`dateClosed`), popularity scores (`popularityScore`), and cache indicators (`fromCache`) **Breaking change**: Field names have changed to camelCase. Examples: `review_count` → `reviewCount`, `country_code` → `countryCode`, `is_open` → `isOpen`. All fields except `name` and `position` are now optional. Learn more in our [ChatGPT map documentation](/docs/api-reference/endpoint/chatgpt/map). **Ad extraction for ChatGPT** ChatGPT responses now support structured ad extraction when advertising content is displayed. This opt-in feature lets you monitor ads shown in ChatGPT responses. **Opt-in feature with additional cost** Ad extraction is disabled by default. Set `include.ads: true` to extract ads from ChatGPT responses. Enabling `ads`, `rawResponse`, or `searchQueries` (or any combination) adds +2 credits to the base cost. **Features**: * **Brand information**: Advertiser brand name, URL, and favicon * **Carousel cards**: Multiple promotional cards with titles, descriptions, images, and destination URLs * **Attribution tracking**: All URLs include ChatGPT attribution parameters * **Structured data**: JSON structure Learn more in our [ChatGPT documentation](/docs/api-reference/endpoint/chatgpt/ads). **Google News endpoint** We've added a new endpoint for extracting structured news articles from Google News (`/v1/monitor/google/news`). **Features**: * **News article extraction**: Titles, links, snippets, sources, and publication dates * **Multi-page scraping**: Scrape up to 10 pages of news results per request * **Country-specific results**: Localized news from 250 countries worldwide * **Device targeting**: Desktop or mobile news results * **Thumbnail images**: Article thumbnails when available * **Raw HTML access**: Optional full page HTML for custom parsing Learn more in our [Google News documentation](/docs/api-reference/endpoint/monitor-google-news). **Inline products for ChatGPT** ChatGPT responses now include inline products: individual product references that appear embedded in the response text, separate from shopping cards. This feature supports OpenAI's new [product discovery capabilities](https://openai.com/index/powering-product-discovery-in-chatgpt/) announced in ChatGPT. **Model availability**: Inline products currently appear when ChatGPT uses the `gpt-5-3` model. ChatGPT automatically selects which model to use for each request. What's new: * Inline product extraction: Individual products referenced in comparison tables, featured recommendations, or inline mentions * Rendering hints: Display guidance with `render_as` field ("inline", "hero", or "block") * Cite references: Each product includes a unique cite ID for cross-referencing with text Learn more in our [ChatGPT inline products documentation](/docs/api-reference/endpoint/chatgpt/inline-products). **Async queue monitoring and visibility** New [`GET /v1/async/status`](/docs/api-reference/endpoint/get-async-status) endpoint provides real-time visibility into your async queue health, including queued tasks, processing tasks, and concurrency usage. **Use cases**: * Monitor queue health and processing capacity * Decide on plan upgrades based on actual concurrency usage * Debug task delays by identifying queue bottlenecks * Throttle task submission based on current queue size Learn more in our [async status documentation](/docs/api-reference/endpoint/get-async-status). **Shopping cards for Copilot** Copilot responses now automatically include structured shopping cards when product information is detected: * **Product identifiers**: Unique product ID, group ID, and brand group ID for precise tracking * **Product details**: Name, description, brand, and seller information * **Specifications**: Configurable product attributes like Color, Size with available values * **Images**: Multiple product images with titles * **Pricing**: Structured price data with amount, currency, and symbol * **Ratings**: Product ratings with review counts * **Price tracking**: Flag indicating if price tracking is available No additional parameters are required. Shopping cards are automatically detected and included when Copilot returns product information. Learn more in our [Copilot documentation](/docs/api-reference/endpoint/copilot/shopping-cards). **Raw response now available for all event stream engines** The `rawResponse` field is now available for Copilot, Grok, Gemini, and Perplexity endpoints at no additional cost. **Example usage:** ```json theme={null} { "prompt": "Your query here", "model": "GROK", "include": { "rawResponse": true } } ``` Raw response data provides full streaming event payloads, so you can see how these AI models generate their responses. **Global coverage milestone: 250 countries supported** cloro now supports **250 countries worldwide** across all endpoints. Our geographical infrastructure spans from major markets to remote territories, so you can monitor AI responses and search results from almost anywhere in the world. This includes territories like Antarctica (AQ), remote islands like Bouvet Island (BV), and regions like the British Indian Ocean Territory (IO). **Total coverage**: * **250 countries**: Complete ISO 3166-1 alpha-2 country code coverage * **All endpoints supported**: Geographical availability across ChatGPT, Perplexity, Grok, Gemini, Copilot, AI Mode, AI Overview, and Google Search * **Model-specific availability**: Each model has its own country support based on provider restrictions The [countries endpoint](/docs/api-reference/endpoint/countries) remains the authoritative source for current country availability. Always query with your target model to verify supported locations. **Massive geographical expansion for Perplexity, Copilot, Gemini, and Grok** We've expanded country coverage for Perplexity, Copilot, Gemini, and Grok to match the availability of ChatGPT. **Country coverage by model**: * **ChatGPT**: 196 countries (unchanged) * **Perplexity**: 196 countries (expanded from 74) * **Grok**: 195 countries (expanded from 74) * **Gemini**: 195 countries (expanded from 73) * **Copilot**: 193 countries (expanded from 71) * **AI Mode**: 211 countries (unchanged) * **AI Overview**: 230 countries (unchanged) * **Google Search**: 250 countries (unchanged) The [countries endpoint](/docs/api-reference/endpoint/countries) remains the authoritative source for current country availability. Always query with your target model to verify supported locations before deploying. **Grok search queries and model extraction** Grok responses now include two new fields for better insight into how responses are generated: * **`searchQueries`**: Array of search queries Grok used to gather information for the response * **`model`**: The model identifier used to generate the response (e.g., "grok-3", "grok-4-auto") **Example response**: ```json theme={null} { "result": { "text": "Here are the best sneakers under $100...", "searchQueries": [ "best sneakers under $100 2026", "best budget sneakers under $100 2026" ], "model": "grok-4-auto" } } ``` Learn more in our [Grok documentation](/docs/api-reference/endpoint/monitor-grok). **Copilot country restrictions** Italy (IT) and Brazil (BR) have been disabled for the Copilot endpoint. Microsoft has placed these regions behind a login wall, making them unavailable for anonymous scraping. Use the [countries endpoint](/docs/api-reference/endpoint/countries) with `model=copilot` to verify current country availability before deploying. **Improved ChatGPT source metadata** ChatGPT sources now include enhanced metadata for better source attribution: * **`datePublished`**: Publication date of the source article (e.g., "May 22, 2025") * **`label`**: Now contains the article title instead of the domain name * **`description`**: Now contains a content snippet instead of the combined date and title Learn more in our [ChatGPT documentation](/docs/api-reference/endpoint/chatgpt/sources). **ChatGPT geographical expansion** ChatGPT is now available in 200 countries worldwide. The [countries endpoint](/docs/api-reference/endpoint/countries) remains the authoritative source for current country availability. Always query with `model=chatgpt` to verify supported locations before deploying. **Sponsored ad extraction for Google Search** Google Search endpoint now automatically extracts sponsored ad results from search results pages. **Features**: * **Ad placement tracking**: Ads are extracted from both top and bottom of search results with `blockPosition` indicating placement * **Complete ad details**: Title, URL, displayed URL, domain, description, and position within ad block * **Ad sitelinks**: Extended ad sitelinks with titles, URLs, and descriptions when available * **Multi-page support**: Ads are extracted across all requested pages **Response structure**: ```json theme={null} { "result": { "ads": [ { "position": 1, "blockPosition": "top", "title": "Best Programming Laptops - Shop Now", "url": "https://example.com/programming-laptops", "page": 1, "displayedUrl": "example.com/laptops", "domain": "example.com", "description": "Shop our selection of high-performance laptops...", "sitelinks": [ { "url": "https://example.com/gaming-laptops", "title": "Gaming Laptops", "description": "High-performance laptops for gaming" } ] } ] } } ``` No additional parameters required. Ads are automatically included when present on search results pages. Learn more in our [Google Search documentation](/docs/api-reference/endpoint/google/sponsored-ads). **Isolation of ChatGPT citation data** We've improved how source information is structured and exposed in ChatGPT responses: **Citation pills**: Inline citations that appear within ChatGPT responses where specific sources are referenced. Each citation pill includes: * **URL**: Direct link to the cited source * **Label**: Title or label of the citation * **Description**: Summary of the cited content * **Domain**: Source domain (e.g., "example.com") * **Date published**: ISO 8601 date string when the source was published * **Citation pill ID**: Unique identifier for the citation **Sources footnote field**: The `sources` array now includes a `footnote` boolean field that indicates whether a source appears in the main window footnote (i.e., the sources pill), to distinguish between primary and secondary sources. Previously, the citation and source pill data was only accessible by parsing the markdown response. Now you can access it directly through structured JSON objects without needing to parse markdown links. No additional parameters required. Citation pills and the footnote field are automatically included when available in ChatGPT responses. Learn more in our [ChatGPT documentation](/docs/api-reference/endpoint/chatgpt/citation-pills). **Updated Google Search pricing structure** We've updated the Google Search endpoint baseline pricing to a more predictable tiered structure for multi-page requests. **What's changed:** * **Multi-page pricing** is now tiered instead of per page: * 2-3 pages: +1 credit (was +2-4 credits) * 4-10 pages: +2 credits (was +6-18 credits) * **Base cost and AI Overview** remain unchanged: * Base request: 3 credits * AI Overview addon: +2 credits **New pricing examples:** * 1 page, no AI Overview: **3 credits** (unchanged) * 1 page, with AI Overview: **5 credits** (unchanged) * 3 pages, with AI Overview: **6 credits** (was 9 credits) * 10 pages, with AI Overview: **7 credits** (was 23 credits) Costs are more predictable, with reduced pricing for bulk multi-page extraction. **Map entries for ChatGPT** ChatGPT responses now automatically include structured map entries when business or place information is detected. Use cases: * **Local business monitoring**: Track restaurants, stores, and service providers * **Location intelligence**: Extract ratings, reviews, and contact details * **Competitive analysis**: Monitor local businesses across different regions * **Directory building**: Create structured business listings Map entries include business details: * **Business information**: Name, category, description * **Social proof**: Rating score, review count * **Contact details**: Website URL, phone number * **Location data**: Position/ranking in results * **Navigation**: Directions URL (when available) No additional parameters are required. Map entries are automatically detected and included when ChatGPT returns local business or place information. Learn more in our [ChatGPT documentation](/docs/api-reference/endpoint/chatgpt/map). **Added 16 new countries** We've expanded our geographical coverage with 16 new countries available for monitoring across all non-Google endpoints: ``` BG - Bulgaria BY - Belarus CY - Cyprus EE - Estonia GE - Georgia KE - Kenya KG - Kyrgyzstan LT - Lithuania LU - Luxembourg LV - Latvia MA - Morocco MT - Malta PE - Peru TJ - Tajikistan UZ - Uzbekistan ``` Remember to always check the [model-specific country list](/docs/api-reference/endpoint/countries) before deploying to ensure availability for your target endpoint, as some models have country-specific restrictions. **Google Search and AI Mode updates** We've updated the Google Search and AI Mode endpoints: **Removed parameters:** * `city` parameter temporarily removed from Google Search and AI Mode endpoints * No customers were using this parameter, and `country`-level localization was sufficient for all use cases **New parameter:** * `device` parameter added to AI Mode endpoint * Options: `desktop` (default) or `mobile` * Same device targeting available in Google Search **Gemini geographical expansion** Gemini now supports EU countries and additional regions, bringing geographical coverage to parity with Copilot, Perplexity, and Grok. The [countries endpoint](/docs/api-reference/endpoint/countries) remains the authoritative source for current country availability. Always query with `model=gemini` to verify supported locations before deploying: ```bash theme={null} curl "https://api.cloro.dev/v1/countries?model=gemini" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **Grok is now available** Grok is now available as a new provider. Key features: * **Supported in most countries** (remember to check the [model-specific country list](/docs/api-reference/endpoint/countries)) * **Source metadata** with preview text, search engine display text, site information, creator details, and image URLs * **Real-time web integration** for current information and breaking news * **Response formats**: text, HTML, and Markdown Getting started: ```bash theme={null} curl -X POST https://api.cloro.dev/v1/monitor/grok \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "What are the latest developments in quantum computing?", "country": "US" }' ``` See the [Grok API documentation](/docs/api-reference/endpoint/monitor-grok) for full details and examples. We've expanded our geographical coverage across all Google endpoints: * **Google Search**: Now available in all 250 countries worldwide * **AI Mode**: Expanded from 58 to 211 countries * **AI Overview**: Expanded from 59 to 230 countries Remember to check the [model-specific country list](/docs/api-reference/endpoint/countries) before deploying to ensure availability for your target endpoint. **Added 11 new countries** Expanded our geographical coverage with 11 new countries available for monitoring: ``` BD - Bangladesh CO - Colombia EG - Egypt FI - Finland HK - Hong Kong KZ - Kazakhstan NG - Nigeria PH - Philippines PK - Pakistan VE - Venezuela VN - Vietnam ``` Most new locations start with low capacity, which will be increased based on demand. Remember to check the [model-specific country list](/docs/api-reference/endpoint/countries) before deploying to ensure availability for your target endpoint. **Defined async task retention policy** `COMPLETED` and `FAILED` async tasks are stored for **24 hours** after completion. HTML URLs also expire after 24 hours from generation (aligned with task retention). **More fields extracted for AI Mode and AI Overview** Google AI Mode and AI Overview endpoints now automatically extract rich structured data: * **Places in AI Mode**: Location information with ratings, reviews, price levels, addresses, Google Places IDs, and operating status * **Shopping cards in AI Mode**: Structured product data with currency-aware pricing, merchant details, ratings, and purchase links * **Video content in AI Overview**: Videos with thumbnails, titles, duration, platform info, and source attribution No additional parameters required. All structured data is automatically detected and included when available in responses. Learn more in [AI Mode](/docs/api-reference/endpoint/monitor-aimode) and [AI Overview](/docs/api-reference/endpoint/google/ai-overview). Since location availability depends on the model, we have enhanced the `/v1/countries` endpoint with model filtering: * **Model-specific filtering** - Get countries available for specific AI providers using the `model` query parameter * **Supported models** - Filter by: `aimode`, `aioverview`, `chatgpt`, `copilot`, `gemini`, `google`, or `perplexity` **Usage examples**: ```bash theme={null} # Get all countries curl "https://api.cloro.dev/v1/countries" # Get countries available for ChatGPT curl "https://api.cloro.dev/v1/countries?model=chatgpt" # Get countries available for Perplexity curl "https://api.cloro.dev/v1/countries?model=perplexity" ``` Learn more in our [countries endpoint documentation](/docs/api-reference/endpoint/countries). We've added support for Google Gemini (`/v1/monitor/gemini`). You can now extract structured data from Gemini responses with source citations. **Features**: * **Text extraction** - Get the generated response text * **Source citations** - Structured list of sources with URLs, snippets, and confidence levels * **Markdown & HTML** - Optional output formats **Usage example**: ```json theme={null} { "prompt": "Explain quantum computing", "country": "US", "include": { "markdown": true } } ``` Learn more in our [Gemini documentation](/docs/api-reference/endpoint/monitor-gemini). The HTML code is now stored externally to avoid crowding the response payload. **Changes**: * HTML URLs now expire after 24 hours from generation * Storage URLs updated to use `https://storage.cloro.dev/results/` format **Important note**: If you need to preserve HTML content longer than 24 hours, download it promptly after receiving the response. **Example response**: ```json theme={null} { "result": { "html": "https://storage.cloro.dev/results/c45a5081-808d-4ed3-9c86-e4baf16c8ab8/page-1.html" } } ``` Learn more in our [Making Requests documentation](/docs/guides/making-requests). **New Google Search endpoint** We're excited to announce the launch of our Google Search endpoint (`/v1/monitor/google`) for extracting structured data from Google search results. **Features**: * **Organic results** - Extract titles, links, snippets, and positions from search results * **People Also Ask** - Capture questions and answers with page tracking * **Related searches** - Get related search query suggestions * **AI Overview** - Extract Google's AI-generated summaries with source attribution (optional) * **Multi-page scraping** - Scrape up to 10 pages per request with the `pages` parameter * **Country-specific results** - Localized search results across many countries * **Hyperlocal targeting** - City-specific search results using canonical city names with automatic UULE conversion * **Raw HTML access** - Option to retrieve full page HTML for custom parsing **Usage example**: ```json theme={null} { "query": "best laptops for programming", "pages": 3, "country": "US", "include": { "aioverview": { "markdown": true } } } ``` Learn more in our [Google search documentation](/docs/api-reference/endpoint/monitor-google). **Enhanced retry logic for synchronous requests** We have the following changes in our internal retry mechanism: * Increased retry attempts from 5 to 10 for synchronous requests * Added 5-minute maximum duration timeout to prevent indefinite waiting * Requests canceled by clients are charged for resources consumed (same behavior as before) This automatic retry handling **removes the need for client-side timeout logic**. More details in the [Error handling](/docs/guides/error-handling) section. **Configurable HTML response for all parsers** HTML content in parser responses is now optional and configurable across all monitoring endpoints. **Changes**: * Added `include.html` parameter to all parser endpoints (ChatGPT, Perplexity, Copilot, AI Mode, AI Overview) * A URL to the HTML content is now only included when explicitly requested with `"include.html": true` * Responses are smaller and faster when HTML is not needed * Markdown conversion continues to work regardless of HTML inclusion **Usage example**: ```json theme={null} { "prompt": "Your query here", "include": { "html": true, "markdown": true } } ``` This reduces response size and improves performance when your application doesn't need the full HTML content. **Asynchronous requests and webhooks** You can now make asynchronous requests for async tasks. When you make an async request, you'll receive a `taskId`, the task will then be processed in the background. You can retrieve the results in two ways: * **Webhooks**: Provide a `webhook.url` in your request, and we'll send the results to your endpoint as soon as they're ready. * **Polling**: Use the new [`GET /v1/async/task/{taskId}`](/docs/api-reference/endpoint/get-task-status) endpoint to check the status of your task and retrieve the results when it's complete. This works for serverless environments or applications that can't wait for a task to complete. Learn more in our [Async Requests documentation](/docs/guides/making-requests). **More information in Perplexity response** The Perplexity API endpoint has been expanded to reflect extra fields in the Perplexity response. The endpoint now automatically extracts structured data objects based on query intent: **Shopping data extraction**: * Product information with titles, descriptions, pricing, and ratings * Multiple merchant offers and availability status * Product variants, images, and specifications * Shopping cards with promotional tags and categorization **Media content extraction**: * Video content with thumbnails, dimensions, and source attribution * Image extraction with metadata and sizing information * Support for YouTube, stock photos, and other media platforms **Travel and location data**: * Hotel listings with ratings, reviews, amenities, and pricing * Place information with categories, coordinates, and contact details * Address data, map URLs, and image galleries **Search intelligence**: * Related query suggestions for follow-up searches * Internal search query tracking showing how responses are generated * Citation processing and source attribution The documentation now provides schema definitions for all response objects, including field types, descriptions, and examples. No API changes were required. These features were already available and are now documented. Learn more in the updated [Perplexity API documentation](/docs/api-reference/endpoint/monitor-perplexity). **Entity extraction for ChatGPT** ChatGPT responses now include automatic entity extraction when specific items, products, brands, or concepts are identified. Use cases: * **Product recognition**: Identify and extract product mentions * **Brand tracking**: Monitor brand references across ChatGPT responses * **Concept analysis**: Extract named entities for semantic analysis * **Content classification**: Categorize response content based on extracted entities Each entity includes: * **Type**: Entity type identifier (e.g., "product", "software") * **Name**: Entity name or title (e.g., "adidas Grand Court Lo", "Nike Dunk Low Retro SE") Entity extraction works alongside shopping cards. No additional parameters are required. Entities are automatically detected and included when available. Learn more in our [ChatGPT documentation](/docs/api-reference/endpoint/chatgpt/entities). **Shopping cards for ChatGPT** ChatGPT responses now automatically include structured shopping cards when product information is detected. Use cases: * **Product monitoring**: Track prices, ratings, and availability over time * **Price tracking**: Monitor price changes across different regions * **Competitive intelligence**: Extract structured product data for analysis * **Commerce integration**: Build price comparison and recommendation systems Shopping cards include product details: * **Product information**: Name, brand, description, specifications * **Pricing data**: Current price, original price, discount information * **Commercial details**: Multiple merchant offers, availability, checkout options * **Media assets**: Product images, checkout-specific images * **Ratings**: Ratings, review counts, rating citations with sources * **Offer details**: Merchant-specific pricing, promotional tags, shipping costs The shopping cards feature supports: * **Multi-offer products**: Compare different merchants for the same product * **Price breakdown**: Pricing including base, shipping, tax, and total * **Promotional content**: Tags, tooltips, and special offers * **Media**: Multiple product images and checkout assets * **Availability tracking**: Real-time stock and checkout status No additional parameters are required. Shopping cards are automatically detected and included when available. Learn more in our [ChatGPT documentation](/docs/api-reference/endpoint/chatgpt/shopping-cards). **New dedicated endpoints** Each AI model now has its own dedicated endpoint for better performance and clearer documentation: * [Extract Google AI Overview](/docs/api-reference/endpoint/google/ai-overview) (via `POST /v1/monitor/google` with `include.aioverview`) * [Extract Microsoft Copilot](/docs/api-reference/endpoint/monitor-copilot) (`POST /v1/monitor/copilot`) * [Extract Perplexity](/docs/api-reference/endpoint/monitor-perplexity) (`POST /v1/monitor/perplexity`) * [Extract Google AI Mode](/docs/api-reference/endpoint/monitor-aimode) (`POST /v1/monitor/aimode`) * [Extract ChatGPT](/docs/api-reference/endpoint/monitor-chatgpt) (`POST /v1/monitor/chatgpt`) **Added Google AI Mode** Google's AI Mode results are now supported. You can extract structured data from ChatGPT, Perplexity, Copilot, and AI Mode using the same unified interface. To monitor AI Mode, set `"model": "AIMODE"` in your API requests: ```json theme={null} { "prompt": "What do you know about Acme Corp?", "model": "AIMODE", "country": "US" } ``` All existing features work with AIMODE, including: * Structured data extraction with sources & citations * Localization across different countries * Markdown format support * Citation grouping and processing Learn more in our [API Reference](/docs/api-reference/endpoint/monitor-aimode). **Added `include.rawResponse` parameter** You can now access the underlying AI provider payload in monitoring responses (ChatGPT-only). Enable it with `"include.rawResponse": true` to receive the full provider response alongside cloro's structured output. ```json theme={null} { "prompt": "What do you know about Acme Corp?", "model": "CHATGPT", "include": { "markdown": true, "rawResponse": true } } ``` Learn more in our [API Reference](/docs/api-reference/endpoint/monitor-chatgpt). **Added Microsoft Copilot** Microsoft Copilot (formerly Bing Chat) is now supported. You can now extract structured data from ChatGPT, Perplexity, and Copilot using the same unified interface. To use Copilot, simply set `"model": "COPILOT"` in your API requests: ```json theme={null} { "prompt": "What do you know about Acme Corp?", "model": "COPILOT", "country": "US" } ``` All existing features work with Copilot, including: * Structured data extraction with sources & citations * Localization across different countries * Markdown format support * Citation grouping and processing Learn more in our [API Reference](/docs/api-reference/endpoint/monitor-copilot). **Added Perplexity** Perplexity is now supported. You can now extract structured data from both ChatGPT and Perplexity models using the same unified interface. To use Perplexity, simply set `"model": "PERPLEXITY"` in your API requests: ```json theme={null} { "prompt": "What do you know about Acme Corp?", "model": "PERPLEXITY", "country": "US" } ``` All existing features work with Perplexity, including: * Structured data extraction with sources & citations * Localization across different countries * Markdown format support Learn more in our [API Reference](/docs/api-reference/endpoint/monitor-aimode). **Added `searchQueries` field to responses** All AI monitoring responses now include a `searchQueries` field that shows the search terms used to generate the AI response. ```json theme={null} { "success": true, "result": { "text": "The name \"Acme Corporation\" is used in various contexts...", "searchQueries": [ "What is Acme Corporation?", "Acme Corp company overview", "Acme Corporation products and services" ] } } ``` Learn more in our [API Reference](/docs/api-reference/endpoint/monitor-aimode). **Added `include.markdown` parameter** You can now receive markdown-formatted content in AI monitoring responses. Perfect for documentation workflows and content management systems. ```json theme={null} { "prompt": "What do you know about Acme Corp?", "model": "CHATGPT", "include": { "markdown": true } } ``` Learn more in our [API Reference](/docs/api-reference/endpoint/monitor-aimode). **cloro API launch** The cloro AI monitoring API is live. Extract structured data from ChatGPT, Perplexity, Microsoft Copilot, Google AI Mode and other AI models about your brand or any topic across different regions. Key features: * AI response monitoring across different countries * Structured data extraction with sources and citations * Support for text and HTML formats * Real-time AI model tracking Get started with our [welcome guide](/docs/) or explore the [API reference](/docs/api-reference). # Account & organizations Source: https://cloro.dev/docs/guides/account How cloro user accounts and organizations relate, how to manage team members, and how to delete either from the dashboard and what that removes. Every cloro account has two layers. Your user account is your personal login, profile, and email address. Your organization is the workspace that holds your plan, credits, API keys, and teammates, and all API access and [billing](/docs/guides/billing) belong to it rather than to you personally. Both are managed by Clerk, our authentication provider, so you manage them from the [dashboard](https://dashboard.cloro.dev) rather than through the API — there is no API endpoint for either. ## Where to manage each * **Your user account**: open the user menu at the bottom of the sidebar (your name and email), then click **Account**. Dashboard sidebar user menu open, showing the Account and Log out options * **Your organization**: open the organization switcher at the top of the sidebar, then click **Manage organization**. Organization switcher open, showing the organization list and the Manage organization option **Manage organization** is a paid-plan feature. On the [free tier](/docs/guides/billing#free-tier) the switcher shows **Upgrade to invite your team** instead, so organization settings aren't reachable until you subscribe. Your user account settings are always available. ## Team members Free-tier accounts are **single-seat** — just the account owner. Inviting teammates requires a paid plan, and **every paid plan includes unlimited team members**, so upgrade from the [dashboard](https://dashboard.cloro.dev) and invite as many as you need. Teammates share the organization's credits, concurrency limit, and API keys — there are no per-seat charges and no per-member credit allocations. ## Deleting your account Deletion is permanent: nothing is recoverable afterwards. Your user and your organization are deleted separately, and differently. ### Deleting your organization Organization settings don't include a delete option — the **General** tab covers your profile, verified domains, and leaving the organization. To have the organization itself deleted, contact support. Organization settings General tab, showing the profile, verified domains, and leave organization options Once the organization is deleted, cloro: * **Cancels your subscription** immediately — no refund is issued for the remaining cycle or unused credits * **Deletes every API key** in the organization. Requests then fail with `401 Unauthorized` (keys are cached briefly, so calls may still succeed for up to a minute) * **Deletes all organization data** — memberships, subscription record, credit balance, and credit usage history * **Discards queued and completed async tasks**, which are only retrievable for [24 hours](/docs/guides/making-requests/async) in any case If you only want to stop being charged, you don't need the organization deleted at all — [cancel the subscription](/docs/guides/billing#managing-your-subscription) from the dashboard instead. ### Deleting your user account Deleting your own user removes your profile, removes you from every organization you belong to, and takes your address off cloro's mailing list. In the **Account** modal, open the **Security** tab and use **Delete account**. Account settings Security tab, showing password, active devices, and the Delete account button Deleting your user does **not** cancel your organization's subscription — billing continues on the same cycle. If you're closing your account to stop being charged, cancel the subscription first. If you're the only admin of an organization, contact support before deleting your user — otherwise nobody is left who can manage it. ## Common questions ### Will deleting my account stop my subscription? No. Deleting your user leaves the organization and its billing untouched — [cancel the subscription](/docs/guides/billing#managing-your-subscription) from the dashboard instead, without deleting anything. ### I'm on the free tier and want my organization gone. What do I do? Delete your user account. A free-tier organization carries no subscription, so nothing is being charged and there's nothing further you need to do. ### Can you restore a deleted organization? No. Deletion is permanent, and API keys, credit history, and usage logs are removed with it. To start again, sign up and create a new organization — you'll get new API keys and start on the [free tier](/docs/guides/billing#free-tier). ### What happens if multiple accounts at my company subscribe separately? cloro allows one active subscription per domain (e.g., `yourcompany.com`). If multiple accounts sign up under the same domain, we may reach out to consolidate them. If you need multiple API keys for different environments or teams, you can generate them directly from the dashboard — multiple keys can be active under a single subscription. ### Do I have to delete my account to switch plans? No. Upgrades and downgrades are handled from the [dashboard](https://dashboard.cloro.dev) with no interruption to your API keys — see [Billing & credits](/docs/guides/billing#managing-your-subscription). # Authentication Source: https://cloro.dev/docs/guides/authentication Learn how to authenticate with the cloro API using Bearer tokens, manage API keys securely, and handle authentication errors in your requests. The cloro API uses Bearer token authentication. Create and manage your keys in the [dashboard](https://dashboard.cloro.dev). ## Using your API key The `Bearer` token in the `Authorization` header **is your API key** — there's no separate token exchange or OAuth flow. Copy the key from the dashboard and pass it directly with the `Bearer` prefix on every request: ``` Authorization: Bearer YOUR_API_KEY ``` Never expose your API key in client-side code or public repositories, and never share it with unauthorized users. Keep it in a secrets manager or an environment variable, not your source tree. ```bash cURL theme={null} curl -X POST https://api.cloro.dev/v1/monitor \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt": "Your prompt here", "model": "CHATGPT"}' ``` ```python Python theme={null} import requests headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } response = requests.post( "https://api.cloro.dev/v1/monitor", headers=headers, json={"prompt": "Your prompt here", "model": "CHATGPT"} ) ``` ```javascript JavaScript theme={null} const headers = { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }; const response = await fetch("https://api.cloro.dev/v1/monitor", { method: "POST", headers: headers, body: JSON.stringify({ prompt: "Your prompt here", model: "CHATGPT" }), }); ``` ```go Go theme={null} client := &http.Client{} req, _ := http.NewRequest("POST", "https://api.cloro.dev/v1/monitor", body) req.Header.Add("Authorization", "Bearer YOUR_API_KEY") req.Header.Add("Content-Type", "application/json") resp, _ := client.Do(req) ``` ## Authentication errors If authentication fails, you'll receive a `401 Unauthorized` response: ```json theme={null} { "success": false, "error": "Invalid or missing API key" } ``` Common causes: * **Missing Bearer prefix**: include `Bearer` before your API key * **Invalid API key**: check that you're using the correct key * **Expired API key**: some API keys have expiration dates * **Rate limit exceeded**: you've exceeded your plan's rate limit ## Environment variables ```bash .env theme={null} CLORO_API_KEY=your_api_key_here ``` ```python Python theme={null} import os api_key = os.getenv('CLORO_API_KEY') ``` ```javascript Node.js theme={null} const apiKey = process.env.CLORO_API_KEY; ``` ```go Go theme={null} apiKey := os.Getenv("CLORO_API_KEY") ``` ## API key management If you suspect a key has been compromised, revoke it and generate a new one in the [dashboard](https://dashboard.cloro.dev). You can also create multiple keys for different purposes: one for your live application, one for local development and testing, one for automated testing and deployment pipelines, and individual keys for team members. ## Need help? * Check our [API Reference](/docs/api-reference) * Get help: paid plans via the in-dashboard support widget, free tier via the **Ask Assistant** button in these docs # Billing & credits Source: https://cloro.dev/docs/guides/billing How cloro billing works: subscription plans, credit allocation and expiration, payment cycles, overage policies, and managing your subscription. cloro uses a subscription-based billing model with monthly credit allocations and concurrency limits based on your plan tier. ## Subscription plans Find our plan options, concurrency levels and pricing at [cloro.dev](https://cloro.dev). All plans include: * Access to all API endpoints * Response headers with credit and concurrency tracking * Dashboard access for usage monitoring and billing management ## Free tier Every account starts on the **free tier** — 500 credits, refreshed at the start of each month — with 1 concurrent request and access to all endpoints and providers. Complete onboarding actions to earn recurring bonus credits on top each month: leave a G2 review (+500/mo), add a backlink (+500/mo), and introduce yourself in the [r/cloroapi](https://www.reddit.com/r/cloroapi/) welcome thread (+200/mo). Each bonus persists only while the underlying action stays live. No card is required, so it's the way to evaluate the API and cover light, ongoing use. When you need more, subscribe to a paid plan from the [dashboard](https://dashboard.cloro.dev): **Lite** (\$30/mo) is the entry tier, and Hobby, Starter, Growth, and Business scale up credits and concurrency (and lower the per-credit rate) from there. See the [pricing page](https://cloro.dev/#pricing) for current numbers. Free-tier accounts are single-seat, and every paid plan includes unlimited team members — see [Team members](/docs/guides/account#team-members). ## How billing works cloro bills only for successful requests: credits are deducted when a request completes successfully (`success: true`), and failed requests cost zero, so retries are free. The exception is a request you cancel client-side, which is charged for work already done (see [Error handling](/docs/guides/error-handling#retries-and-cancellation)). ### Credit allocation * Credits are allocated at the start of each billing cycle * Credit consumption varies by endpoint and features used (see [Providers](/docs/guides/providers)) * Sync monitor requests include a +2 credit surcharge (see [Providers](/docs/guides/providers#sync-request-surcharge) for details) * For [batch requests](/docs/api-reference/endpoint/create-batch-tasks), credits are tracked per task — each task's result carries its own `creditsToCharge` and `creditsCharged` values * Each response returns the amount charged and your remaining balance in the `X-Credits-Charged` and `X-Credits-Remaining` [headers](/docs/guides/concurrency#credit-headers) ### Credit expiration **Important**: Credits expire at the end of each billing cycle and do not roll over to the next month. If you have unused credits at the end of your billing cycle, they will be lost. Plan your usage to make the most of your subscription. ### Billing cycles * All plans operate on monthly billing cycles * Billing cycles start on the date you subscribe and renew on the same date each month * Renewals are automatic unless you cancel ## Managing your subscription All subscription management is handled through the [dashboard](https://dashboard.cloro.dev): * **Upgrade plans**: Switch to higher-volume tiers with immediate effect. When upgrading, we prorate the price for the remaining days and set your available balance to the full new plan allocation minus credits already consumed this cycle — unused credits from the old plan are preserved, not reset. * **Downgrade plans**: Downgrades take effect at the start of your next billing cycle — your current plan continues until cycle end, then the lower plan activates. No proration charge. * **Monitor usage**: View credit balance, usage graphs, and billing history * **Cancel subscription**: Service continues until the end of the current billing cycle, but no refunds are issued for unused credits. ### Update billing details and access invoices To change your payment method, update your billing address or tax ID, or download past invoices: 1. Go to the [dashboard](https://dashboard.cloro.dev) and open **Billing** from the left sidebar. 2. In the **Current plan** card, click **Manage your billing**. 3. The customer portal opens, where you can: * Update your payment method * Edit your billing address, company name, and tax ID * View and download invoices and receipts * Update the email address invoices are sent to Changes made in the customer portal apply to future invoices. The dashboard customer portal only shows invoices from **May 2026 onwards**. To download earlier invoices, use the [legacy billing portal](https://billing.stripe.com/p/login/00waEZbpK9Qf0Zl8w06c000) and sign in with the email on your old account. ### Closing your account Cancelling stops future charges but keeps your account. To delete your organization or your user account instead, see [Account & organizations](/docs/guides/account#deleting-your-account) — deleting the organization also cancels the subscription. ## Standard vs. enterprise plans ### Standard plans Standard plans (Lite, Hobby, Starter, Growth, Business) are available through the dashboard with immediate activation. For higher monthly volumes or annual-contract terms, see [Enterprise plans](#enterprise-plans) below. The [pricing page](https://cloro.dev/#pricing) has current tier counts, credit allocations, and concurrency limits. * **Self-service**: Subscribe and upgrade directly through the [dashboard](https://dashboard.cloro.dev) * **No overage charges**: Service stops when you run out of credits * **Flexible**: Upgrade anytime and cancel at the end of the billing cycle * **Immediate access**: Start using the API right away ### Enterprise plans Enterprise comes in two forms: * **Self-serve monthly tiers** — subscribed through the dashboard, billed like [Standard plans](#standard-plans): hard credit cap, no overages, cancel anytime. * **Annual contracts** — 12-month commitments, available from the entry Enterprise tier upward and required above the self-serve ceiling. In the overlap band the choice is yours. See the [pricing page](https://cloro.dev/#pricing) for the current ceiling and tier details. **Annual contract benefits** (self-serve monthly tiers get the baseline allocation at the same dollar amount with none of these): * **Overages allowed**: service continues after your monthly credit allocation is exhausted; overage credits are billed at your contracted per-credit rate in the following billing cycle * **20% more credits per month** (17% lower effective per-credit rate vs. the equivalent self-serve monthly tier) * **1.3× concurrency**: your concurrent-request limit is boosted vs. the baseline for the same dollar tier * **Access to non-public endpoints**: available on request, subject to cloro's discretion * **Price lock for the 12-month term**: fees and per-credit rate are fixed for the contract duration * **Net 7-day payment**: pay by bank transfer or ACH direct debit **Getting started with enterprise:** At or above the entry Enterprise tier an annual contract is worth considering; above the self-serve ceiling it is the only route, and subscribing requires sales. Contact [our sales team](mailto:info@cloro.dev) and you'll be sent the contract for signature. Upon receiving the signed contract, your Enterprise plan will be configured in less than 24 hours. ## Common questions ### Do unused credits roll over to the next month? No — see [credit expiration](#credit-expiration). ### How can I check my current credit balance? **Programmatically**, call [`GET /v1/credits`](/docs/api-reference/endpoint/get-credits) — it returns your current balance (`remaining`), credits per cycle (`perCycle`), and the cycle reset date (`cycleResetsAt`). This works for async-only workloads too, so you don't need to make a billable sync request just to read your balance. Every sync monitor response also returns the balance in the `X-Credits-Remaining` [header](/docs/guides/concurrency#credit-headers). In the **[dashboard](https://dashboard.cloro.dev/)** you can see: * Current credit balance * Usage for the current billing cycle * Projected spend based on current usage * Historical usage data ### What happens when I run out of credits? When you reach your credit limit, your API requests will be rejected with a `403 Insufficient Credits` error. Async tasks you already queued are affected too — they are re-checked when the scheduler reaches them and move to `FAILED` rather than waiting for a top-up. See [what happens to async tasks when your credits run out](/docs/guides/making-requests/async#what-happens-to-async-tasks-when-my-credits-run-out). To continue using the API: 1. Switch to a higher-tier monthly plan (takes effect immediately) 2. Wait for the next billing cycle (credits replenish on your renewal date) 3. Contact sales for an annual Enterprise contract — annual plans include overages, so service continues at your contracted rate instead of stopping at the cap Whenever you hit 80% and 100% of your monthly allocation you will be informed via email. ### What does the free tier include, and can free-tier users contact support? The [free tier](#free-tier) gives you 500 credits refreshed each month — plus recurring bonus credits for onboarding actions you complete — so you can evaluate the API and run light workloads before subscribing. Free-tier accounts are capped at **1 concurrent request** (see [Rate & concurrency limits](/docs/guides/concurrency#rate-limits-vs-concurrency-limits)) but otherwise have access to all endpoints and providers. Free-tier support is self-serve: ask the docs AI assistant with the **Ask Assistant** button on any docs page. Paid plans get direct support through the in-dashboard chat widget. For volume, concurrency, or annual-contract questions, email [info@cloro.dev](mailto:info@cloro.dev) or see [Enterprise plans](#enterprise-plans). ### Is there a pay-as-you-go option? No. cloro uses a credit-based subscription model — there is no pay-as-you-go or per-request pricing. All plans include a fixed monthly credit allocation, and credits not used by the end of the cycle expire and do not roll over. If you want a low-commitment start, the [free tier](#free-tier) covers evaluation and the **Lite** plan (\$30/mo) is the cheapest subscription. ### Are credits charged when shopping cards or ads fields return empty? Yes. Credits are deducted whenever a response returns `"success": true`, regardless of whether optional fields like `shoppingCards`, `ads`, or `sources` contain data. These fields are opportunity-based: cloro attempts to extract them when the AI surface serves that content. An empty `shoppingCards: []` is a valid result, not a failure. Use `success: false` to identify requests that should not be charged. ### Do I need a VAT registration to sign up? No. You can start on the free tier or subscribe to any plan without a VAT number — VAT is charged on the invoice where applicable. Once you have a VAT number, add it under **Billing → Manage your billing** in the [dashboard](https://dashboard.cloro.dev) and it will appear on future invoices. ### Can I see request history beyond the current billing cycle? The credit usage log in the dashboard supports a custom date range picker. ### Can I get a refund if I cancel my subscription? No refunds are issued when you cancel your subscription. When you cancel: * Service continues until the end of the current billing cycle * You retain access to all remaining credits during that period * No charges occur for subsequent billing cycles # Rate & concurrency limits Source: https://cloro.dev/docs/guides/concurrency Understand cloro's rate and concurrency limits, read the response headers, and process many requests efficiently with async patterns and concurrent workers. Concurrency is the number of API requests you can have in progress simultaneously. Your plan sets the number of slots: with 10, an 11th request sent while 10 are still processing gets a [rate limit error](#rate-limits-vs-concurrency-limits) rather than queueing. ## Rate limits vs. concurrency limits [Free tier](/docs/guides/billing#free-tier) accounts are limited to **1 concurrent request**. Upgrade for multi-threaded workloads. See [the pricing table](https://cloro.dev/#pricing) for concurrency limits per plan. cloro applies two types of limit depending on the endpoint: | Limit type | Endpoints affected | How it works | | ---------------------- | ----------------------------------- | ------------------------------------------------------- | | **Rate limits** | All endpoints (`/v1/*`) | 1,000 requests per second per endpoint | | **Concurrency limits** | Monitor endpoints (`/v1/monitor/*`) | Based on your subscription plan (simultaneous requests) | Monitor endpoints are subject to both. ## Monitoring concurrency with headers Each response includes HTTP headers to help you manage your API usage: | Header | Description | | ------------------------ | ---------------------------------------------------- | | `X-Concurrent-Limit` | Total concurrent requests allowed by your plan | | `X-Concurrent-Current` | Number of requests currently processing | | `X-Concurrent-Remaining` | Available concurrent slots when request was received | On a 20-slot plan with 3 requests in flight: ``` X-Concurrent-Limit: 20 X-Concurrent-Current: 3 X-Concurrent-Remaining: 17 ``` ## Monitoring rate limits with headers All endpoints include rate limit headers in each response: | Header | Description | | ----------------------- | ------------------------------------------- | | `X-RateLimit-Limit` | Maximum requests per second allowed (1,000) | | `X-RateLimit-Remaining` | Remaining requests available in this second | The limit is **per endpoint per API key** — each `/v1/*` path has its own independent 1,000 RPS bucket, and the counter resets every second. After one request to `/v1/monitor/chatgpt`: ``` X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 999 ``` ## Credit headers Monitor endpoints (`/v1/monitor/*`) also return the credit balance and the amount charged for each request: | Header | Description | | --------------------- | ------------------------------------------- | | `X-Credits-Remaining` | Number of credits remaining in your account | | `X-Credits-Charged` | Number of credits charged for this request | Track spend per request and alert before you run low: ```javascript theme={null} function trackCredits(response) { const creditsRemaining = response.headers["x-credits-remaining"]; const creditsCharged = response.headers["x-credits-charged"]; console.log(`Credits charged: ${creditsCharged}`); console.log(`Credits remaining: ${creditsRemaining}`); // Alert if running low if (creditsRemaining < 100) { console.warn("Low credit balance. Consider topping up"); } } ``` These headers are only returned on sync `/v1/monitor/*` responses. For async-only workloads, read your balance from [`GET /v1/credits`](/docs/api-reference/endpoint/get-credits) instead. For how credits are allocated, expire, and are priced, see [Billing & credits](/docs/guides/billing) and [Providers](/docs/guides/providers). ## Latency header Every response carries `X-Latency-Ms` — the milliseconds the API spent on the request, from arrival to the start of the response. It excludes network transit, so wall-clock timing on your side is always higher. ``` X-Latency-Ms: 3420 ``` Async tasks report the equivalent in the task body as `latencyMs` — see [asynchronous requests](/docs/guides/making-requests/async#step-2-receive-the-results). ## Using headers for optimization Size each batch against the slots you have left: ```javascript theme={null} function checkConcurrencyUsage(response) { const limit = parseInt(response.headers["x-concurrent-limit"]); const current = parseInt(response.headers["x-concurrent-current"]); const remaining = parseInt(response.headers["x-concurrent-remaining"]); console.log(`Concurrency: ${current}/${limit} (${remaining} available)`); // Adjust your batch size based on remaining slots return Math.min(remaining, 5); // Don't exceed 5 requests per batch } ``` Or throttle before you exhaust the per-second budget: ```javascript theme={null} async function checkRateLimit(response) { const limit = parseInt(response.headers["x-ratelimit-limit"]); const remaining = parseInt(response.headers["x-ratelimit-remaining"]); console.log(`Rate limit: ${remaining}/${limit} requests remaining`); // If you're running low on requests, wait before continuing if (remaining < 50) { const waitTime = 1000; // Wait 1 second for the counter to reset console.log(`Rate limit nearly exceeded. Waiting ${waitTime}ms...`); await new Promise((resolve) => setTimeout(resolve, waitTime)); } return remaining; } ``` When you do hit a `429`, retry with exponential backoff rather than immediately re-sending: ```javascript theme={null} async function makeRequestWithRetry(requestFn, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await requestFn(); } catch (error) { if (error.status === 429 && i < maxRetries - 1) { const delay = Math.pow(2, i) * 1000; // Exponential backoff await new Promise((resolve) => setTimeout(resolve, delay)); continue; } throw error; } } } ``` See [Error handling](/docs/guides/error-handling#rate-limits-429) for the full 429 error shapes. ## Implementation patterns ### Pattern 1: Async with webhooks For large-scale processing, submit every task concurrently (one request per task) and handle results via webhooks — cloro queues them for you, so you don't have to batch: ```javascript Node.js (axios) theme={null} import axios from "axios"; const API_KEY = process.env.API_KEY; const TASK_API = "https://api.cloro.dev/v1/async/task"; async function submitTasks(tasks, webhookUrl) { // Send API requests concurrently (one request per task) await Promise.all( tasks.map((task) => axios.post( TASK_API, { taskType: "CHATGPT", webhook: { url: webhookUrl }, payload: task, }, { headers: { Authorization: `Bearer ${API_KEY}` }, } ) ) ); } // Webhook handler (Express.js) app.post("/webhook-handler", (req, res) => { const { task, response } = req.body; console.log(`Task ${task.id} completed: ${response.text.slice(0, 100)}...`); // Process your result here saveResult(task.id, response); // Always respond quickly res.status(200).send(); }); // Usage const tasks = [ { prompt: "Analyze market trends", country: "US" }, { prompt: "Research competitors", country: "US" }, // ... hundreds more ]; submitTasks(tasks, "https://your-app.com/webhook-handler"); ``` ```python Python (requests) theme={null} import requests import asyncio import json API_KEY = "YOUR_API_KEY" TASK_API = "https://api.cloro.dev/v1/async/task" async def submit_tasks(tasks, webhook_url): # Send API requests concurrently (one request per task) submit_tasks = [] for task in tasks: submit_tasks.append( requests.post( TASK_API, json={ "taskType": "CHATGPT", "webhook": {"url": webhook_url}, "payload": task }, headers={"Authorization": f"Bearer {API_KEY}"} ) ) # Wait for all submissions to complete responses = await asyncio.gather(*[asyncio.to_thread(req) for req in submit_tasks]) print("All tasks submitted") # Usage tasks = [ {"prompt": "Analyze market trends", "country": "US"}, {"prompt": "Research competitors", "country": "US"}, # ... hundreds more ] asyncio.run(submit_tasks(tasks, "https://your-app.com/webhook-handler")) # Webhook handler example (Flask) """ from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/webhook-handler', methods=['POST']) def handle_webhook(): data = request.json task = data.get('task', {}) response = data.get('response', {}) print(f"Task {task.get('id')} completed: {response.get('text', '')[:100]}...") # Process your result here save_result(task.get('id'), response) return '', 200 """ ``` ```bash cURL theme={null} #!/bin/bash API_KEY="YOUR_API_KEY" TASK_API="https://api.cloro.dev/v1/async/task" WEBHOOK_URL="https://your-app.com/webhook-handler" # Submit tasks individually (cloro handles concurrency automatically) submit_task() { local prompt="$1" local country="$2" curl -s -X POST "$TASK_API" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"taskType\": \"CHATGPT\", \"webhook\": {\"url\": \"$WEBHOOK_URL\"}, \"payload\": { \"prompt\": \"$prompt\", \"country\": \"$country\" } }" & } # Example usage - send API requests concurrently tasks=( "Analyze market trends|US" "Research competitors|US" "Extract pricing data|US" "Summarize industry reports|US" "Identify key trends|US" # ... add more tasks as needed ) echo "Submitting ${#tasks[@]} tasks..." # Submit all tasks in background (no need to limit concurrency) for task_data in "${tasks[@]}"; do IFS='|' read -r prompt country <<< "$task_data" submit_task "$prompt" "$country" done # Wait for all submissions to complete wait echo "All tasks submitted successfully" ``` ### Pattern 2: Concurrent workers For real-time processing where you want immediate results, run multiple workers that make direct API calls: ```javascript Node.js (axios) theme={null} import axios from "axios"; const API_KEY = process.env.API_KEY; const API_URL = "https://api.cloro.dev/v1/monitor/chatgpt"; async function makeRequest(id, prompt) { const start = Date.now(); try { const response = await axios.post(API_URL, { prompt: prompt, country: "US", }, { headers: { Authorization: `Bearer ${API_KEY}` } }); const latency = Date.now() - start; console.log(`Request #${id}: Success (${latency}ms)`); // Monitor concurrency usage const limit = parseInt(response.headers["x-concurrent-limit"]); const current = parseInt(response.headers["x-concurrent-current"]); const remaining = parseInt(response.headers["x-concurrent-remaining"]); return { success: true, latency, data: response.data, usage: { limit, current, remaining } }; } catch (error) { const latency = Date.now() - start; console.log(`Request #${id}: Failed (${latency}ms)`); if (error.response?.status === 429) { console.log(`Rate limited - ${error.response.headers["retry-after"] || "unknown"} seconds`); } return { success: false, latency, error: error.message }; } } async function runConcurrentRequests(prompts, concurrency = 10) { console.log(`Starting ${prompts.length} requests with ${concurrency} concurrent workers\n`); const startTime = Date.now(); const results = []; let requestId = 0; // Worker function async function worker() { while (requestId < prompts.length) { const id = ++requestId; const result = await makeRequest(id, prompts[requestId - 1]); results.push(result); } } // Run concurrent workers await Promise.all( Array(concurrency).fill(0).map(() => worker()) ); const duration = Date.now() - startTime; const successful = results.filter(r => r.success).length; const rateLimited = results.filter(r => r.error?.includes('Rate limited')).length; console.log("\n" + "=".repeat(40)); console.log(`Total: ${prompts.length}`); console.log(`Success: ${successful} (${((successful/prompts.length)*100).toFixed(1)}%)`); console.log(`Rate limited: ${rateLimited}`); console.log(`Duration: ${(duration/1000).toFixed(1)}s`); console.log(`RPS: ${(prompts.length/duration*1000).toFixed(1)}`); console.log("=".repeat(40)); return results; } // Usage const prompts = [ "What is AI and how does it work?", "Explain machine learning basics", "What are neural networks?", "How does deep learning work?", "What is natural language processing?", // ... add more prompts as needed ]; runConcurrentRequests(prompts, 5) // Start with conservative concurrency .then(results => console.log(`Completed processing`)) .catch(console.error); ``` ```python Python (requests + asyncio) theme={null} import asyncio import aiohttp import time from typing import List, Dict, Any class ConcurrentWorker: def __init__(self, api_key: str, api_url: str): self.api_key = api_key self.api_url = api_url async def make_request(self, session, id: int, prompt: str): start_time = time.time() try: async with session.post( self.api_url, json={ "prompt": prompt, "country": "US" }, headers={ "Authorization": f"Bearer {self.api_key}" } ) as response: data = await response.json() latency = (time.time() - start_time) * 1000 # Monitor concurrency usage limit = int(response.headers.get("x-concurrent-limit", 0)) current = int(response.headers.get("x-concurrent-current", 0)) remaining = int(response.headers.get("x-concurrent-remaining", 0)) print(f"Request #{id}: Success ({latency:.0f}ms)") return { "success": True, "latency": latency, "data": data, "usage": {"limit": limit, "current": current, "remaining": remaining} } except Exception as e: latency = (time.time() - start_time) * 1000 print(f"Request #{id}: Failed ({latency:.0f}ms)") return { "success": False, "latency": latency, "error": str(e) } async def run_concurrent_requests(self, prompts: List[str], concurrency: int = 10): print(f"Starting {len(prompts)} requests with {concurrency} concurrent workers\n") start_time = time.time() results = [] request_id = 0 async with aiohttp.ClientSession() as session: async def worker(): nonlocal request_id while request_id < len(prompts): id = request_id + 1 result = await self.make_request(session, id, prompts[request_id]) results.append(result) request_id += 1 # Run concurrent workers await asyncio.gather(*[worker() for _ in range(concurrency)]) duration = time.time() - start_time successful = sum(1 for r in results if r["success"]) rate_limited = sum(1 for r in results if "rate limited" in r.get("error", "").lower()) print("\n" + "=" * 40) print(f"Total: {len(prompts)}") print(f"Success: {successful} ({(successful/len(prompts)*100):.1f}%)") print(f"Rate limited: {rate_limited}") print(f"Duration: {duration:.1f}s") print(f"RPS: {len(prompts)/duration:.1f}") print("=" * 40) return results # Usage async def main(): worker = ConcurrentWorker("YOUR_API_KEY", "https://api.cloro.dev/v1/monitor/chatgpt") prompts = [ "What is AI and how does it work?", "Explain machine learning basics", "What are neural networks?", # ... add more prompts as needed ] results = await worker.run_concurrent_requests(prompts, concurrency=5) print(f"Completed processing") if __name__ == "__main__": asyncio.run(main()) ``` ```bash cURL theme={null} #!/bin/bash API_KEY="YOUR_API_KEY" API_URL="https://api.cloro.dev/v1/monitor/chatgpt" TOTAL_REQUESTS=10 CONCURRENCY=5 # Function to make a single request make_request() { local id=$1 local prompt=$2 local start_time=$(date +%s%3N) response=$(curl -s -w "\n%{http_code}\n%{time_total}\n%{x-concurrent-limit}\n%{x-concurrent-current}\n%{x-concurrent-remaining}" \ -X POST "$API_URL" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d "{\"prompt\":\"$prompt\",\"country\":\"US\"}" \ 2>/dev/null) # Parse response http_code=$(echo "$response" | sed -n '2p') time_total=$(echo "$response" | sed -n '3p') limit=$(echo "$response" | sed -n '4p') current=$(echo "$response" | sed -n '5p') remaining=$(echo "$response" | sed -n '6p') body=$(echo "$response" | sed -n '1p') latency=$(echo "$time_total * 1000" | bc) if [[ "$http_code" == "200" ]]; then echo "Request #$id: Success (${latency%.*}ms) - Usage: $current/$limit ($remaining available)" else echo "Request #$id: Failed (${latency%.*}ms) - HTTP $http_code" fi } # Worker function that processes multiple requests worker() { local worker_id=$1 local max_requests=$2 while true; do # Get next request ID from shared counter id=$(wget -qO- "http://localhost:8080/next-id" 2>/dev/null || echo "0") if [[ $id -gt $max_requests ]]; then break fi prompt="Query $id: What are the benefits of concurrent processing?" make_request "$id" "$prompt" done } # Simple ID counter using files (you could use Redis or a database) echo "1" > /tmp/next_id # Start workers for ((i=1; i<=CONCURRENCY; i++)); do worker $i $TOTAL_REQUESTS & done # Wait for all workers to complete wait echo -e "\n$(printf '=%.0s' {1..40})" echo "Load test completed" # Cleanup rm -f /tmp/next_id ``` ## Common questions ### Why am I getting 429 rate limit errors? Two causes: **Concurrency limit exceeded** (monitor endpoints only) — more simultaneous requests than your plan's slots. Watch `X-Concurrent-Remaining`, queue requests in your application, retry with exponential backoff, or [upgrade your plan](https://cloro.dev/#pricing). **Rate limit exceeded** (all endpoints) — over 1,000 requests per second to a single endpoint. Watch `X-RateLimit-Remaining`, spread requests over time, or move non-time-sensitive work to the [async queue](/docs/guides/making-requests/async). See the [error handling guide](/docs/guides/error-handling#rate-limits-429) for the error responses. ### How do I check my concurrency limit? Read `X-Concurrent-Limit` on any monitor response, or call the [async status endpoint](/docs/api-reference/endpoint/get-async-status) for your account's concurrency stats. ### Can I increase my concurrency limit? Yes. Self-serve plans can be upgraded directly in the [dashboard](https://dashboard.cloro.dev) — the new limit applies immediately, no support ticket required. If you need concurrency above the highest self-serve tier, email [info@cloro.dev](mailto:info@cloro.dev) for an enterprise quote. ### Does higher concurrency delay my logs or dashboards? No. Dashboard log ingestion runs independently from request processing. If logs look delayed during heavy load, the cause is usually batching on the dashboard side, not concurrency — entries normally surface within a minute. ### Can I burst above my concurrency limit? No. The limit is hard — the (N+1)th simultaneous request gets a `429` immediately rather than queueing. Use the [async API](/docs/guides/making-requests/async) if you want cloro to handle queueing for you instead of managing burst capacity yourself. ### What's the best way to handle large batches of requests? For non-time-sensitive batches, use [Pattern 1: Async with webhooks](#pattern-1-async-with-webhooks): send everything concurrently, let cloro queue it, and take results on the webhook. When you need results in real time, use [Pattern 2: Concurrent workers](#pattern-2-concurrent-workers): stay within your plan's concurrency limit, watch `X-Concurrent-Remaining`, and back off exponentially on 429s. Either way, submit through the [batch endpoint](/docs/api-reference/endpoint/create-batch-tasks) rather than one call per task. See [how to submit many requests in one call](/docs/guides/making-requests#how-do-i-submit-many-requests-in-one-call) for its limits. # Error handling Source: https://cloro.dev/docs/guides/error-handling Understanding API errors, HTTP status codes, error response formats, retry logic, and troubleshooting strategies for all cloro monitoring endpoints. The cloro API uses standard [HTTP status codes](#http-status-codes) and consistent [error response formats](#error-response-formats) across all endpoints. ## HTTP status codes | Status Code | Meaning | Description | | ----------- | --------------------- | ------------------------------------------------------------ | | `200` | Success | Request completed successfully | | `400` | Bad Request | Request validation failed | | `401` | Unauthorized | Authentication error (missing/invalid API key) | | `403` | Forbidden | Insufficient permissions or credits | | `404` | Not Found | Endpoint/route not found | | `409` | Conflict | Resource conflict | | `429` | Too Many Requests | Concurrent or rate limit exceeded (depends on endpoint type) | | `499` | Client Closed Request | Request was canceled by client | | `500` | Internal Server Error | Server-side error | | `502` | Bad Gateway | External service error | ## Error response formats Most errors use a nested `error` object: ```json theme={null} { "error": { "code": "ERROR_CODE", "message": "Human-readable error message", "details": { // Additional context-specific information }, "timestamp": "2025-01-15T12:00:00.000Z" } } ``` Validation errors (`400`) instead use a flat shape with a per-field `details` array: ```json theme={null} { "success": false, "error": "Request validation failed", "details": [ { "field": "prompt", "message": "Prompt cannot be empty" } ] } ``` Internal errors (`500`) may arrive in either shape, so handle both — `{"success": false, "error": "Maximum retries exceeded"}` as well as the nested `INTERNAL_SERVER_ERROR` object. ## Error codes ### Authentication (401) | Error Code | Message | Cause | | ---------------------------- | -------------------------- | --------------------------------- | | `MISSING_API_KEY` | Missing or invalid API key | No API key provided in request | | `INVALID_API_KEY_FORMAT` | Invalid API key format | API key format is incorrect | | `INVALID_OR_EXPIRED_API_KEY` | Invalid or expired API key | API key is invalid or has expired | ### Permissions and credits (403) | Error Code | Message | Cause | | -------------------------- | ------------------------ | -------------------------------- | | `INSUFFICIENT_PERMISSIONS` | Insufficient permissions | API key lacks required scopes | | `INSUFFICIENT_CREDITS` | Insufficient credits | Account has insufficient credits | Check your balance on sync requests via the `X-Credits-Remaining` header, or at any time — including from async-only workloads — via [`GET /v1/credits`](/docs/api-reference/endpoint/get-credits). ### Rate limits (429) Monitor endpoints (`/v1/monitor/*`) enforce your plan's concurrency limit: ```json theme={null} { "error": { "code": "CONCURRENT_LIMIT_EXCEEDED", "message": "Concurrent limit exceeded", "details": { "limit": 10 }, "timestamp": "2025-01-15T12:00:00.000Z" } } ``` All endpoints (`/v1/*`) additionally enforce a 1,000/sec per-endpoint rate limit: ```json theme={null} { "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Rate limit exceeded", "details": { "limit": 1000, "window": "1s" }, "timestamp": "2025-01-15T12:00:00.000Z" } } ``` Watch `X-RateLimit-Remaining` and the concurrency headers to stay inside both. The rate-limit counter resets every second, so brief throttling clears it; sustained concurrency pressure needs a queue on your side. **Batch endpoint errors** The [batch task creation endpoint](/docs/api-reference/endpoint/create-batch-tasks) uses a partial success model. Individual tasks can fail with per-task error codes (`VALIDATION_ERROR`, `RESOURCE_ALREADY_EXISTS`, `INSUFFICIENT_CREDITS`) inside a `200` response. See the [batch endpoint documentation](/docs/api-reference/endpoint/create-batch-tasks#per-task-error-codes) for details. ### External service (502) ```json theme={null} { "error": { "code": "EXTERNAL_SERVICE_ERROR", "message": "External service error: OpenAI", "details": { "service": "OpenAI" }, "timestamp": "2025-01-15T12:00:00.000Z" } } ``` ## Retries and cancellation cloro retries transient failures automatically, up to 10 attempts, stopping when the request succeeds or the attempts are exhausted. You do not need your own timeout logic. Still add exponential backoff on `500` and `502` — automatic retries cover transient failures, and a client-side retry adds a second layer for the ones that get through. If they persist, check the status page and contact support. **Canceled requests are charged.** A `499` means you closed the connection, but the processing done before that point is billed — cloro charges for resources consumed, not just delivered results. Retries you issue are billed as new requests too. ## Empty responses from AI providers Occasionally a request completes normally — `200` on sync, `COMPLETED` on async — but the provider returned no usable answer: a short canned message instead of content, and no sources. Safety refusals, age-restricted topic refusals, provider-side errors, truncated answers, and account notices all arrive this way. It is upstream behavior, and no parameter prevents it. The automatic retries above do not cover it — an empty response is a successful delivery of what the provider returned, so detecting and retrying it is a client-side decision. **Detect it structurally**: `sources` is empty **AND** the answer body is very short (under \~160 characters). Do not match the message text — it is localized across a dozen or more languages, and providers reword and translate it, including their own product names. **Retry once when you detect one.** Most categories are not tied to your prompt, so the same prompt commonly succeeds on the next attempt. **The exception is age-restricted and policy topics such as alcohol or gambling**: those refusals track the subject of the prompt, so retrying converts far fewer of them and mostly spends credits. Rates vary by provider and shift as each one updates its models and policies — see the provider's endpoint page for measured figures, for example [Gemini](/docs/api-reference/endpoint/monitor-gemini#some-responses-come-back-with-no-answer-how-often-and-what-should-i-do). ## Validation rules | Field | Rule | Error message | | ------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ----------------------------------------------- | | `prompt` | 1–10,000 characters | "Prompt cannot be empty" / "Prompt is too long" | | `country` | **Uppercase** ISO 3166-1 alpha-2 (e.g. `"US"`, `"GB"`); lowercase is rejected | "Invalid country code" | | `include.markdown`, `include.rawResponse`, `include.searchQueries` | Boolean | "Expected boolean" | ## Common questions ### Why are some of my requests failing with 500 errors? Upstream provider problems, a request that doesn't match the API specification, or brief infrastructure disruptions. Handle them with the backoff described in [retries and cancellation](#retries-and-cancellation). ### A request succeeded but the answer is empty. Is that a bug? No — the provider returned a short canned message instead of an answer, and we returned it as received. See [empty responses from AI providers](#empty-responses-from-ai-providers) for detection and retry guidance. ### Where do I see logs for successful requests? Every monitor request — sync and async — is logged to the [dashboard](https://dashboard.cloro.dev) with the request id, provider, prompt, country, status code, credits charged, and latency. Successful (`2xx`) requests show up alongside failures so you can audit usage end-to-end. Entries typically appear within a minute; if a request seems missing, refresh — heavy load occasionally delays ingestion but does not drop entries. ### Is there a bulk export of request logs? No. The dashboard shows logs in a paginated view only — there is no CSV export or bulk download. For log analysis at scale, log the `X-Request-ID` response header on every API call alongside your own metadata at the point of call. This gives you a permanent audit trail keyed to cloro's internal request IDs for support lookups. ### Can I read the request latency from the API response? Yes. Every response carries the API's processing time for that HTTP call in the [`X-Latency-Ms`](/docs/guides/concurrency#latency-header) header. Async task processing time is a separate figure, reported as [`task.latencyMs`](/docs/guides/making-requests/async#step-2-receive-the-results). Neither includes network transit, so a wall-clock measurement around your call is always higher. ### An async task returned `200 OK` but the result looks wrong. What does that mean? `200 OK` from `GET /v1/async/task/{taskId}` only confirms the task record exists. The task's outcome lives inside `task.status` — see [task states](/docs/guides/making-requests/async#step-1-make-an-api-request) for the full lifecycle and [the `COMPLETED` vs. upstream-error FAQ](/docs/guides/making-requests/async#a-task-came-back-completed-but-the-result-looks-like-an-upstream-error-what-happened) for how to detect degraded provider responses. ### What's the expected success rate for API requests? Above 99% on average, varying with upstream provider stability, geographic region, and time of day. Handle the remainder like any 5xx: cloro's automatic retries cover most, and exponential backoff on your side catches the rest. # Making requests Source: https://cloro.dev/docs/guides/making-requests Compare the two cloro request modes — synchronous and asynchronous — including credit cost, latency, concurrency limits, and guidance on which mode to use. cloro offers two ways to call the API: synchronous requests, where you wait for the result on the same connection, and asynchronous requests, where you submit a task and retrieve the result later. ## Synchronous requests Synchronous endpoints return the full result in the same HTTP response, so the request has to fit inside your client's timeout. See [Synchronous requests](/docs/guides/making-requests/sync) for the request and response structure, common parameters, optional response formats, and code examples. ## Asynchronous requests Asynchronous endpoints accept a task, return a `taskId` immediately, and process the work in the background. You retrieve the result via webhook (recommended) or by polling. See [Asynchronous requests](/docs/guides/making-requests/async) for the two-step submit-and-fetch flow, task states, queue limits, and request prioritization. ## When to use which | Use case | Choose | | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | Time-sensitive request that needs an immediate answer | Synchronous | | Serverless environment with short execution time limits | Asynchronous | | Submitting large batches (up to 500 tasks per request) | Asynchronous | | Building a resilient system that shouldn't rely on a long-running connection | Asynchronous | | Small batches needing immediate results | Synchronous with [concurrent workers](/docs/guides/concurrency#pattern-2-concurrent-workers) | ## Common questions ### Why are my requests slower than expected? Latency comes from three places: * Provider response time (primary factor): the upstream provider's processing time varies with their load (peak usage times) and geographic region * Queue depth (async requests only): set by your plan's concurrency limit and your current queue size * Geographic routing: requests route through region-specific infrastructure, which may differ in latency depending on the `country` parameter you specify [Upgrading your plan](https://cloro.dev/#pricing) raises concurrency, which drains the async queue faster. To measure latency, see [Can I read the request latency from the API response?](/docs/guides/error-handling#can-i-read-the-request-latency-from-the-api-response). ### Can I get faster processing for high-volume workloads? Yes — the larger the plan, the greater the concurrency assigned to it. For async requests, higher concurrency means more tasks processed at once and shorter queue waits. For sync requests, it means you can send more simultaneous requests without hitting limits. ### How do I submit many requests in one call? Use the [batch endpoint](/docs/api-reference/endpoint/create-batch-tasks). It takes up to **500 tasks in a single HTTP request**, so you avoid the overhead of 500 round trips, and it validates each task independently — one bad task doesn't block the rest. You can hold up to 100,000 tasks in the queue at a time, and cloro handles queuing, so you don't manage concurrency limits yourself. Retrieve results with webhooks or polling. For which pattern to use once you're submitting at volume — async with webhooks, or sync with concurrent workers — see [the concurrency guide](/docs/guides/concurrency#whats-the-best-way-to-handle-large-batches-of-requests), which has code examples for both. # Asynchronous requests Source: https://cloro.dev/docs/guides/making-requests/async Submit async tasks to cloro: submit-and-fetch flow, webhooks vs polling, task states, priority, queue limits, and clearing queued jobs. See [Making requests](/docs/guides/making-requests) for an overview of both request modes. You submit a task, we return a `taskId`, and the request runs in the background — like a tracking number rather than waiting at the counter. Submit tasks one at a time or [in batches of up to 500](/docs/api-reference/endpoint/create-batch-tasks). Use async when: * Your application has short execution limits, typically in a **serverless environment**. * You want to submit many requests quickly without waiting on each one. * You need resilience that doesn't depend on a single long-running connection. ## Step 1: make an API request Include a `webhook.url` and we notify you when the job is done; omit it and you poll for the result. ```bash cURL theme={null} curl -X POST "https://api.cloro.dev/v1/async/task" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "taskType": "CHATGPT", "priority": 5, "idempotencyKey": "your-custom-identifier-123", "webhook": { "url": "https://your-app.com/webhook-handler" }, "payload": { "prompt": "What is the weather in New York?", "country": "US" } }' ``` ```javascript Node.js (axios) theme={null} import axios from 'axios'; const apiKey = 'YOUR_API_KEY'; const url = 'https://api.cloro.dev/v1/async/task'; const data = { taskType: 'CHATGPT', priority: 5, idempotencyKey: 'your-custom-identifier-123', webhook: { url: 'https://your-app.com/webhook-handler', }, payload: { prompt: 'What is the weather in New York?', country: 'US', }, }; try { const response = await axios.post(url, data, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, }); console.log(response.data); } catch (error) { console.error(error); } ``` ```python Python (requests) theme={null} import requests api_key = 'YOUR_API_KEY' url = 'https://api.cloro.dev/v1/async/task' payload = { "taskType": "CHATGPT", "priority": 5, "idempotencyKey": "your-custom-identifier-123", "webhook": { "url": "https://your-app.com/webhook-handler" }, "payload": { "prompt": "What is the weather in New York?", "country": "US" } } headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.json()) ``` **Identifying your requests with `idempotencyKey`** You can optionally include an `idempotencyKey` in your request. This is a unique string you create that allows you to easily identify and reference your requests in your own system. The `idempotencyKey` must be a **unique string** across your entire account. If you submit a request with an idempotency key that has already been used, the API will return an error: ```json theme={null} { "success": false, "error": { "code": "RESOURCE_ALREADY_EXISTS", "message": "Task already exists", "details": { "field": "idempotencyKey", "value": "abc12312233123441234451112322212222223" }, "timestamp": "2025-12-10T11:41:40.243Z" } } ``` Generate unique keys using UUIDs, timestamps, or a combination of user ID + timestamp to ensure no duplicates. **Failed task key retention**: When a task returns `FAILED`, the `idempotencyKey` is not released — it stays bound for 24 hours, then auto-releases. To retry a failed task within that window, submit the same request with a new key. **Serverless crash recovery**: If your serverless function (Vercel, Lambda) crashes before you can save the `taskId`, you can poll `GET /v1/async/task/{taskId}` using the original `idempotencyKey` to retrieve the result — no resubmission and no double charge. Store the `taskId` as early as possible after receiving the submit response. We'll acknowledge your request and provide a `taskId`. ```json Initial Response theme={null} { "success": true, "task": { "id": "b27a21e1-7c39-4aa2-a347-23e828c426f9", "taskType": "CHATGPT", "status": "QUEUED", "priority": 5, "createdAt": "2025-11-10T15:00:00.000Z", "latencyMs": null, "idempotencyKey": "your-custom-identifier-123" }, "credits": { "creditsToCharge": 10, "creditsCharged": 0 } } ``` Now, you can store this `taskId` and wait for the results. **Understanding task states** Every async task goes through four possible states: * `QUEUED`: The request is received and waiting its turn to be processed. Tasks are processed by [priority](#request-prioritization) first, then in FIFO (first-in, first-out) order within the same priority level. * `PROCESSING`: The request is actively being processed by our system. The AI provider is generating your response. * `COMPLETED`: The request finished successfully. The final response is included in the same payload. * `FAILED`: The request failed to complete. This can happen because of rate limits, provider errors, or invalid input. The initial response always shows status as `QUEUED`. You can track state transitions by polling the task status endpoint or receiving webhook updates. `COMPLETED` and `FAILED` tasks are stored in our system for **24 hours** after completion. During this time, you can retrieve the full results using the task ID. After 24 hours, the task record and its associated response data are permanently deleted from our system. HTML URLs included in responses expire after **24 hours** from generation, regardless of the task's retention status. ## Step 2: receive the results Two ways to retrieve a result. Either way, a finished task carries `task.latencyMs` — the milliseconds from first pickup to the final outcome, excluding the initial queue wait. It is `null` while the task is `QUEUED` or `PROCESSING`, and stays `null` on a task that failed before processing ever started. On a retried task it covers every attempt, including the backoff between them, so it can be far longer than one provider call. Don't read task latency from the `X-Latency-Ms` header on a status call. That header times the status call itself — a few milliseconds — not the task. Task latency is only ever `task.latencyMs`. #### Option A: webhooks (recommended) If you provided a `webhook.url` in your request, we will send an HTTP `POST` to that URL containing the full result as soon as the task reaches a terminal state. See [Webhooks](/docs/guides/webhooks) for the payload shape, retry behavior, signature verification, and troubleshooting. #### Option B: polling Without a webhook URL, `GET` the [task status](/docs/api-reference/endpoint/get-task-status) endpoint with your `taskId`. Once the task completes, the response carries the full result. ```bash cURL theme={null} curl -X GET "https://api.cloro.dev/v1/async/task/YOUR_TASK_ID" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript Node.js (axios) theme={null} import axios from 'axios'; const apiKey = 'YOUR_API_KEY'; const taskId = 'YOUR_TASK_ID'; const url = `https://api.cloro.dev/v1/async/task/${taskId}`; try { const response = await axios.get(url, { headers: { 'Authorization': `Bearer ${apiKey}`, }, }); console.log(response.data); } catch (error) { console.error(error); } ``` ```python Python (requests) theme={null} import requests api_key = 'YOUR_API_KEY' task_id = 'YOUR_TASK_ID' url = f'https://api.cloro.dev/v1/async/task/{task_id}' headers = { "Authorization": f"Bearer {api_key}" } response = requests.get(url, headers=headers) print(response.json()) ``` ## Understanding limits ### Task submission limits At submission we check only two things: * **Credit Limit**: We verify you have enough credits for the task. * **Queue Limit**: Your organization can have a maximum of **100,000 tasks** waiting in the queue. If you exceed this, you will receive a `429 Too Many Requests` error. Please contact our team if you need this limit increased. ### Task processing limits Once your task is in the queue, our scheduler picks it up for processing. This is where your subscription's **concurrency limit** is enforced. For example, if your plan allows 10 concurrent requests, our scheduler will process up to 10 of your tasks in parallel. Tasks are processed by [priority](#request-prioritization) first, then in the order they were received within the same priority level. ### Request prioritization | Parameter | Type | Required | Default | Description | | ---------- | ------- | -------- | ------- | --------------------------------------------------------------- | | `priority` | integer | No | 1 | Task priority level (1-10). Higher numbers are processed first. | Omitting it leaves the task at 1, so existing integrations are unaffected. Monitor how your queue is distributed across levels via the [async status endpoint](/docs/api-reference/endpoint/get-async-status). For practical concurrency patterns and examples, see our [concurrency](/docs/guides/concurrency) documentation. ## Common questions ### How do I cancel pending async tasks? Individual queued tasks cannot be canceled by `taskId` — once a task enters the `QUEUED` state, it stays there until it is either processed (`COMPLETED` / `FAILED`) or removed by a queue-wide clear. To wipe every pending task in one call, use the [`DELETE /v1/async/queue`](/docs/api-reference/endpoint/clear-async-queue) endpoint. It removes all `QUEUED` tasks for your organization and returns the number cleared: ```bash cURL theme={null} curl -X DELETE "https://api.cloro.dev/v1/async/queue" \ -H "Authorization: Bearer YOUR_API_KEY" ``` Behavior to be aware of: * Only `QUEUED` tasks are removed. `PROCESSING` tasks are already in-flight on a worker and keep running — they cannot be recalled. * `COMPLETED` and `FAILED` tasks are left in place and can still be retrieved via [`GET /v1/async/task/{taskId}`](/docs/api-reference/endpoint/get-task-status). * Queued tasks have not been charged yet, so clearing the queue does **not** refund or debit credits. * The call is idempotent — re-running it on an empty queue returns `cleared: 0`. Best practices to avoid unwanted tasks in the first place: * Test with small batches first * Use unique `idempotencyKey` values to prevent duplicate submissions * Implement safeguards in your submission logic * Monitor your queue depth via the [async status endpoint](/docs/api-reference/endpoint/get-async-status) before submitting large batches ### What's the maximum queue depth? Queue depth is limited to **100,000 tasks** per organization. If you exceed this limit, you'll receive a `429 Too Many Requests` error when trying to submit additional tasks. If you need a larger queue for your use case, please contact our team. ### How long do async tasks stay in the queue? Tasks stay in the queue until they are processed, resulting in either `COMPLETED` or `FAILED` status. Check your current queue status using the [async status endpoint](/docs/api-reference/endpoint/get-async-status). ### How do I track the credits consumed by each task? Both the polling response and the webhook payload include a `credits` object: * `creditsToCharge` — the estimated cost shown while the task is `QUEUED` or `PROCESSING` * `creditsCharged` — the actual amount billed once the task reaches `COMPLETED` or `FAILED` Use `creditsCharged` from the terminal state to attribute cost per job. Failed tasks may still incur credits depending on how far processing got. Sync requests [canceled by the client are also charged for work already done](/docs/guides/error-handling#retries-and-cancellation); async tasks cannot be canceled individually once queued, but you can [wipe the whole pending queue](#how-do-i-cancel-pending-async-tasks) in one call. ### What happens to async tasks when my credits run out? Credits are checked twice, and the balance is never reserved in advance: * **At submission.** [`POST /v1/async/task`](/docs/api-reference/endpoint/create-async-task) rejects with `403 INSUFFICIENT_CREDITS` when your balance does not cover that task's cost. [`POST /v1/async/task/batch`](/docs/api-reference/endpoint/create-batch-tasks) instead returns `200` with a per-task `INSUFFICIENT_CREDITS` error for each task that doesn't fit, so a partially affordable batch is partially accepted. * **At scheduling.** Tasks already sitting in `QUEUED` are re-checked when the scheduler picks them up. A task that no longer fits the balance moves to `FAILED` with `creditsCharged: 0` and an `INSUFFICIENT_CREDITS` error — it is not held, retried, or resumed when you top up. Re-submit those tasks after topping up. Both checks are skipped if your organization is on a subscription with overages enabled. Because credits are only deducted when a task completes, the balance from [`GET /v1/credits`](/docs/api-reference/endpoint/get-credits) reflects **completed charges only** — it does not net out `creditsToCharge` for work still queued or processing. To decide whether a submission will fit, subtract your own outstanding `creditsToCharge` from `remaining` rather than trusting `remaining` alone. ### A task came back `COMPLETED` but the result looks like an upstream error. What happened? `COMPLETED` means cloro finished its work and returned what the upstream provider gave us. If the provider returned an error page, a captcha, or a truncated answer, that detail lives inside the `response` payload. Inspect the response body — `FAILED` is reserved for cases where cloro could not produce a result at all (network errors, internal exceptions, repeated upstream timeouts). ### Does async cost more credits than sync? No. The credit cost per request is identical whether you use synchronous or async delivery. The `creditsCharged` field in the terminal task state shows the same cost you would see from a sync call for the same endpoint and parameters. ### The async endpoint feels slow today. What should I check? Call [`GET /v1/async/status`](/docs/api-reference/endpoint/get-async-status) to see your account's queue depth and concurrency usage. If the queue is deep and concurrency is saturated, throughput is plan-bound — see [concurrency](/docs/guides/concurrency#can-i-increase-my-concurrency-limit) for how to raise it. # Synchronous requests Source: https://cloro.dev/docs/guides/making-requests/sync Make synchronous requests to the cloro API: request structure, common parameters, response shape, optional formats, and code examples. cloro API monitoring endpoints share a mostly consistent request and response structure. See [Making requests](/docs/guides/making-requests) for an overview of both request modes. ## Request structure ```json theme={null} { "prompt": "Your query here (1-10,000 characters)", "country": "US", // Required, ISO 3166-1 alpha-2 (uppercase) "include": { // Optional, varies by endpoint "markdown": false, "html": false } } ``` ## Common parameters Most monitoring endpoints share these core parameters: | Parameter | Type | Required | Description | | --------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `prompt` | string | Yes | The query to send to the AI provider (1-10,000 characters) | | `country` | string | Yes | [ISO 3166-1 alpha-2 country code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) (uppercase) for localized results. There is no default — requests without `country` return a validation error | | `state` | string | No | Sub-country state or region code for state-level geo-targeting (e.g., `"CA"`). Currently only supported for `country: "US"`. Supported on ChatGPT, Copilot, Perplexity, and Gemini. | | `include` | object | No | Optional flags for additional response formats | **Google Search endpoint exception** The Google Search endpoint uses `query` instead of `prompt` as the required parameter, since it's designed for search queries rather than AI prompts. See the [Google Search endpoint documentation](/docs/api-reference/endpoint/monitor-google) for details. Country codes must be uppercase (`"US"`, `"GB"`, `"DK"`) — lowercase values return a validation error. For the full list of supported countries per provider, see the [Countries endpoint](/docs/api-reference/endpoint/countries). ## Response structure All successful responses follow this base structure: ```json theme={null} { "success": true, "result": { "text": "AI response text", "sources": [...], "html": "https://cdn.cloro.dev/results/c45a5081-808d-4ed3-9c86-e4baf16c8ab8/page-1.html", // Only included when "include.html": true, expires after 24 hours // Additional fields vary by endpoint } } ``` ## Common response fields | Field | Type | Description | | ---------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `success` | boolean | Always `true` for successful responses | | `result.text` | string | The AI provider's response text | | `result.sources` | array | Array of [sources](#sources-array-structure) referenced in the response | | `result.html` | string \| array | A URL to the full HTML of the response (included when `include.html` is `true`, expires after 24 hours). On Google Search and Google News this is an **array** of URLs, one per scraped page; on every other endpoint it is a single URL string | ## Response headers Every response includes an `X-Request-Id` header. Store it — our support team uses it to locate your request. ``` X-Request-Id: b0864943-5d45-4796-bc64-f052661256f0 ``` For rate limit, concurrency, and credit headers, see [Rate & concurrency limits](/docs/guides/concurrency). ## Sources array structure ```json theme={null} { "position": 1, "url": "https://example.com/article", "label": "Article Title Here", "description": "A snippet or summary of the source content..." } ``` | Field | Type | Description | | ------------- | ------ | ------------------------------------------ | | `position` | number | The position index of the source | | `url` | string | The URL of the source | | `label` | string | The article title of the source | | `description` | string | A snippet or summary of the source content | Some endpoints include additional source fields. See individual endpoint documentation for endpoint-specific source fields. ## Optional response formats Most endpoints support additional response formats through the `include` parameter: ### HTML format * **Parameter**: `include.html` * **Type**: boolean * **Default**: `false` * **Description**: Request a URL to the full HTML of the response — an array of URLs, one per page, on Google Search and Google News. URLs expire after 24 hours, so download the HTML if you need it long-term. * **Cost**: no extra charge ### Markdown format * **Parameter**: `include.markdown` * **Type**: boolean * **Default**: `false` * **Description**: Include response formatted in Markdown * **Cost**: no extra charge Individual endpoints offer their own formats and features — see the endpoint documentation for each, and [Providers](/docs/guides/providers) for endpoint costs and credit information. ## Request examples ```bash theme={null} curl -X POST "https://api.cloro.dev/v1/monitor/chatgpt" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "What do you know about Acme Corp?", "country": "US", "include": { "markdown": true, "html": true } }' ``` ```python theme={null} import requests response = requests.post( "https://api.cloro.dev/v1/monitor/chatgpt", headers={ "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" }, json={ "prompt": "What do you know about Acme Corp?", "country": "US", "include": {"markdown": True, "html": True} } ) result = response.json() print(result['result']['text']) print(result['result']['sources']) if 'html' in result['result']: print(result['result']['html']) if 'markdown' in result['result']: print(result['result']['markdown']) ``` ```javascript theme={null} const response = await fetch("https://api.cloro.dev/v1/monitor/chatgpt", { method: "POST", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ prompt: "What do you know about Acme Corp?", country: "US", include: { markdown: true, html: true }, }), }); const data = await response.json(); console.log(data.result.text); console.log(data.result.sources); if (data.result.html) { console.log(data.result.html); } if (data.result.markdown) { console.log(data.result.markdown); } ``` # Output formats Source: https://cloro.dev/docs/guides/output-formats Learn how to render proof-of-page HTML, request optional text and markdown outputs, and handle inline objects in cloro API responses for LLM analysis. Alongside the parsed JSON, every response can carry the scraped page's HTML — proof of what the AI provider actually displayed — and its answer as text or markdown for LLM pipelines. ## HTML: proof of scraped page When you request HTML from any endpoint using [`include.html: true`](/docs/guides/making-requests/sync#html-format), the response includes a URL to the full scraped page stored on our CDN. This is the page the AI provider actually displayed, so you can present it to your clients as proof of what was scraped. `result.html` is a single URL string on most endpoints. On Google Search and Google News it is an **array** of URLs — one per scraped page — so handle both shapes if you call several endpoints from the same code path. The raw HTML includes browser chrome (headers, footers, sidebars, cookie banners, and scripts). To render a clean version, fetch the HTML from the CDN URL and parse it with the [`@cloro-dev/response-parser`](https://www.npmjs.com/package/@cloro-dev/response-parser) npm package: it auto-detects the AI provider, strips scripts and chrome, and returns sanitized HTML you can render in any framework (React, Vue, Svelte, vanilla JS). See the [response-parser README](https://github.com/cloro-dev/response-parser) for installation, API reference, framework examples, and supported options. The HTML URL expires after 24 hours — download and store the HTML if you need long-term access. Including HTML costs no extra credits. ## Text and markdown: LLM analysis For text analysis or LLM pipelines, use `result.text` or `result.markdown` (with [`include.markdown: true`](/docs/guides/making-requests/sync#markdown-format)) directly. You don't need the response-parser for this. But **special objects are embedded as text** in these fields: when the AI provider displays shopping cards, places, ads, inline products, entities, or map entries, their content appears in `result.text` and `result.markdown`. Sources and footers are **not** included. The structured fields (`result.shoppingCards`, `result.places`, `result.ads`, `result.inlineProducts`, `result.entities`, `result.mapEntries`) hold the same data in clean form. Use them to know exactly what to strip out of the text before sending it to an LLM, and to read object-specific data such as prices, ratings, and URLs. If you pass `result.text` or `result.markdown` directly to an LLM without removing special object content, the model may treat product cards, ads, or place listings as part of the AI's natural language response. ## Common questions ### Why do I see errors when opening the HTML file in my browser? The scraped HTML may include JavaScript from the original AI provider page. When opened directly in a browser, this JavaScript executes and can cause errors, broken layouts, or unexpected behavior. To view the HTML correctly, disable JavaScript in your browser before opening the file: in Chrome, open DevTools (Cmd/Ctrl+Shift+I), press Cmd/Ctrl+Shift+P, and run "Disable JavaScript". Alternatively, use the [`@cloro-dev/response-parser`](https://www.npmjs.com/package/@cloro-dev/response-parser) library which automatically strips all scripts during parsing. # Supported AI providers, features, and credit pricing Source: https://cloro.dev/docs/guides/providers Compare cloro provider support across ChatGPT, Gemini, Copilot, Perplexity, Grok, Google AI Mode, and Google Search, including features and credit costs. cloro extracts structured data from multiple providers. Each request costs credits based on the provider and the features you enable. ## Provider list and base costs | Provider | Base Cost | Key Features | | -------------------------------------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | | **[ChatGPT](/docs/api-reference/endpoint/monitor-chatgpt)** | 5 credits ([see details](#chatgpt-additional-features)) | Shopping cards & inline products (+2), query fan-out (+2), raw response (+2), ads (+2), state targeting (+2), map entries, citation pills | | **[AI Overview](/docs/api-reference/endpoint/google/ai-overview)** | 5 credits | Part of Google Search endpoint, ads included | | **[Gemini](/docs/api-reference/endpoint/monitor-gemini)** | 4 credits | State targeting (+2), markdown & HTML included | | **[AI Mode](/docs/api-reference/endpoint/monitor-aimode)** | 4 credits ([see details](#ai-mode-additional-features)) | Shopping cards, markdown included, expanded product results (+1 per cluster) | | **[Perplexity](/docs/api-reference/endpoint/monitor-perplexity)** | 3 credits | State targeting (+2), shopping cards, query fan-out, markdown included | | **[Copilot](/docs/api-reference/endpoint/monitor-copilot)** | 5 credits | State targeting (+2), shopping cards, query fan-out, markdown included | | ~~**[Grok](/docs/api-reference/endpoint/monitor-grok)**~~ | ~~4 credits~~ | ~~State targeting (+2), query fan-out, markdown included~~ | | **[Google Search](/docs/api-reference/endpoint/monitor-google)** | 3 credits ([see details](#google-search-multi-page-pricing)) | AI Overview and/or PAA AI Overview (+2 total), ads, multi-page (+2 per additional page) | | **[Google News](/docs/api-reference/endpoint/monitor-google-news)** | 3 credits | Multi-page (+2 per additional page, same as Google Search) | ## Detailed pricing ### Sync request surcharge All sync monitor requests (`/v1/monitor/*`) include a **+2 credit surcharge** on top of the base cost and any feature add-ons. Async and batch requests (`/v1/async/*`) are **not** affected. ### Google Search multi-page pricing | Feature | Cost | | -------------------------------------------------- | ------------------- | | Base request (page 1) | 3 credits | | + AI Overview | +2 credits | | + PAA AI Overview | +2 credits | | Any combination of AI Overview and PAA AI Overview | +2 credits | | + Each additional page (pages 2-10) | +2 credits per page | | **3 pages with AI Overview** | **9 credits** | | **10 pages with AI Overview** | **23 credits** | HTML responses are included at no additional cost. Maximum 10 pages per request. Google News uses the same multi-page pricing, without the AI Overview option. Read `X-Credits-Charged` on the response for the exact charge of a request. ### ChatGPT additional features | Feature | Cost | | ----------------------------------------------------------------- | ------------- | | Base request (web search enabled) | 5 credits | | + Raw response | +2 credits | | + Query fan-out | +2 credits | | + Ads | +2 credits | | + Shopping cards & inline products (`include.shopping`) | +2 credits | | Any combination of raw response, query fan-out, ads, and shopping | +2 credits | | **Full response** | **7 credits** | ### AI Mode additional features | Feature | Cost | | ----------------------------------------------------- | ----------------------------------------------- | | Base request | 4 credits | | + Expanded product results (`include.expandProducts`) | +1 credit per product cluster returned (max +6) | Setting `include.expandProducts: true` returns a [`productResults`](/docs/api-reference/endpoint/aimode/product-results) array with per-merchant offers for each product cluster. The surcharge scales with the clusters Google actually returns — three clusters cost 4 + 3 = 7 credits, none costs the base 4. Expansion covers at most 6 clusters per scrape, so the ceiling is 10 credits. The charge is applied after the scrape completes and appears in `X-Credits-Charged` on sync responses. ## Feature comparison ### Query fan-out support Query fan-out reveals the search queries that providers use internally to gather information. Useful for understanding how models break down complex prompts. | Provider | Support | Request Parameter | Cost | | --------------- | --------- | ------------------------------ | -------------------------- | | **ChatGPT** | ✅ Yes | `include.searchQueries: true` | +2 credits | | ~~**Grok**~~ | ~~✅ Yes~~ | ~~None (included by default)~~ | ~~Included in base price~~ | | **Perplexity** | ✅ Yes | None (included by default) | Included in base price | | **Copilot** | ✅ Yes | None (included by default) | Included in base price | | **Gemini** | ❌ No | - | - | | **AI Overview** | ❌ No | - | - | | **AI Mode** | ❌ No | - | - | AI Overview and AI Mode do not expose `result.searchQueries`. Google does not include that data in the rendered HTML it serves to anonymous users. ### Shopping cards support Shopping cards provide structured product information including pricing, ratings, offers, and merchant details. | Provider | Support | Cost | Notes | | --------------- | ------- | ---------- | -------------------------------------------------------------- | | **ChatGPT** | ✅ Yes | +2 credits | Opt-in with `include.shopping: true`. Includes inline products | | **Perplexity** | ✅ Yes | Included | Automatically extracted when available | | **Copilot** | ✅ Yes | Included | Automatically extracted when available | | **AI Mode** | ✅ Yes | Included | Automatically extracted when available | | **Grok** | ❌ No | - | - | | **Gemini** | ❌ No | - | - | | **AI Overview** | ❌ No | - | - | Cards only appear when the prompt is product-related and the provider returns product data — even then they aren't guaranteed. See [Why aren't shopping cards appearing in my responses?](#why-arent-shopping-cards-appearing-in-my-responses) for prompt examples. ### Ads support Ads extraction provides structured advertising data from provider responses, including advertiser branding, destination URLs, and product carousel cards. | Provider | Support | Cost | Notes | | ----------------- | ------- | ---------- | -------------------------------------------------------------- | | **ChatGPT** | ✅ Yes | +2 credits | Opt-in with `include.ads: true`. Brand info and carousel cards | | **Google Search** | ✅ Yes | Included | Always included. Sponsored ads with sitelinks | | **AI Overview** | ✅ Yes | Included | Always included. Text and shopping ads within AI Overview | | **Perplexity** | ❌ No | - | - | | **Copilot** | ❌ No | - | - | | **Grok** | ❌ No | - | - | | **Gemini** | ❌ No | - | - | | **AI Mode** | ❌ No | - | - | ### State-level targeting Pass the USPS two-letter code as `state` alongside `country: "US"` to target a specific state: ```json theme={null} { "prompt": "best electricians near me", "country": "US", "state": "TX" } ``` Call [`GET /v1/states?country=US`](/docs/api-reference/endpoint/states) for the full list of codes (50 states + DC). | Provider | Support | Cost | | ----------------- | --------- | -------------- | | **ChatGPT** | ✅ Yes | +2 credits | | **Copilot** | ✅ Yes | +2 credits | | **Perplexity** | ✅ Yes | +2 credits | | **Gemini** | ✅ Yes | +2 credits | | ~~**Grok**~~ | ~~✅ Yes~~ | ~~+2 credits~~ | | **Google Search** | ❌ No | — | | **AI Overview** | ❌ No | — | | **AI Mode** | ❌ No | — | | **Google News** | ❌ No | — | Google Search, AI Overview, and AI Mode use `location` / `uule` for sub-country targeting instead. Google News supports `country` only — it has no sub-country targeting. ### Other features | Feature | Providers | Cost | Notes | | --------------------- | ----------------------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Sources** | All | Included | All providers include source citations | | **HTML responses** | All | Included | Use `include.html: true` | | **Markdown format** | All | Included | Use `include.markdown: true` | | **Entity extraction** | ChatGPT | Included | Products, brands, and concepts mentioned in responses | | **Map entries** | ChatGPT, Copilot | Included | Business/place information | | **Citation pills** | ChatGPT, Google AI Overview, AI Mode, Copilot, Perplexity, Gemini | Included | Inline citations denormalized one entry per cited source, sharing a `citationPillId` per chip. Not available on Grok (no inline citations upstream) | | **Related links** | Google AI Overview | Included | A chip's "View related links" flyout URLs that are not in `sources`, grouped with their pill via `citationPillId`. See [AI Overview schema](/docs/api-reference/endpoint/google/ai-overview#related-links) | | **Raw response** | ChatGPT | +2 credits | Full streaming events with `include.rawResponse: true` | ## Unsupported providers cloro does not currently scrape the following providers. The list covers the most common requests we receive and is updated as coverage changes. | Provider | Supported? | Reason | | ---------------- | ---------- | -------------------------------------------------------------------------------- | | **Mistral** | ❌ No | No endpoint available today. We evaluate new providers regularly based on demand | | **Claude** | ❌ No | Behind a login wall — access conflicts with Anthropic's terms of service | | **Meta AI** | ❌ No | Behind a login wall — access conflicts with Meta's terms of service | | **DeepSeek** | ❌ No | Behind a login wall — access conflicts with DeepSeek's terms of service | | **Amazon Rufus** | ❌ No | Behind a login wall — access conflicts with Amazon's terms of service | To request a provider, let us know at [r/cloroapi](https://www.reddit.com/r/cloroapi/). We cannot commit to dates and will not scrape services that require authentication. ## Common questions ### How does cloro retrieve data from AI providers? cloro retrieves data from AI surfaces (ChatGPT, Perplexity, Copilot, Gemini, etc.) using real browser sessions routed through proxy infrastructure — not direct API calls. Key implications: * **Geo proxy routing**: each request routes through a proxy in the country (and, for US requests on supported providers, the state) you specify — the AI provider sees that location's anonymous user. * **Model routing**: cloro does not control which model the provider uses; that is the provider's decision (e.g., ChatGPT cycles between `gpt-5-3` and `gpt-5-3-mini` based on its own logic). * **Authentication walls**: cloro cannot access content that requires a logged-in session. If a provider begins requiring login in a region, requests from that region will fail until the provider relaxes the requirement. * **Response fidelity**: the `html` field is the actual page the provider rendered; `result.*` fields are extracted from that page. ### Does the `country` parameter change the response language? No. `country` controls geo-routing, not language. Most providers infer language from the prompt itself, so to get a Spanish response from a Mexico-targeted request, write the prompt in Spanish. Mixed-language prompts (English prompt + non-English country) often work but can produce inconsistent localisation — for best results, match the prompt language to the target country. ### I targeted one country but the response references another — is the proxy broken? Almost always the proxy applied correctly and what you're seeing is model behavior, not routing: 1. **Pre-trained answers leak through.** A model trained heavily on US English content sometimes defaults to US-centric references even when served from another region. The proxy was applied; the model ignored the regional context. 2. **The prompt overrides the geography.** A prompt like "best pizza in Cincinnati" returns Cincinnati results from any proxy. The location affects what the *search layer* sees, not what the user *asked for*. 3. **Sources are the strongest signal.** Inspect `result.sources`. Sources from the targeted country's domains (`.de`, `.fr`, `.com.ua`) confirm the proxy worked. Diverse sources with US-centric prose is model bias, not a routing bug. 4. **HTML proof.** Request `include.html: true` and inspect the rendered page — the chrome (language, currency, regional UI) reflects where the request was actually made from. If sources, HTML chrome, *and* response language all point to the wrong region, open a support ticket with the `taskId` and the country you submitted. ### A country worked last month but now returns 400 errors. What changed? Provider availability shifts week to week as upstream products roll out regional changes. Refresh [`GET /v1/countries?model=`](/docs/api-reference/endpoint/countries) before assuming a regression — the supported list is the source of truth and it does change. If the country still appears in the list but requests keep failing, reach out with the `taskId` — paid plans via the in-dashboard support widget, free tier via the **Ask Assistant** button in these docs. ### Why is ChatGPT (or Copilot) suddenly failing for a specific country? Some AI providers intermittently require login for anonymous users in specific regions, blocking the anonymous session cloro uses. This is not a cloro infrastructure issue — it resolves when the provider adjusts its regional access controls, on a timeline cloro does not control. If your failure rate from a specific country consistently exceeds \~30%, reach out — paid plans via the in-dashboard support widget, free tier via the **Ask Assistant** button in these docs. The status page (`status.cloro.dev`) does not track per-region provider breakdowns. ### Why aren't shopping cards appearing in my responses? Shopping cards only appear when the prompt is related to products or shopping: ```bash theme={null} # ✅ Good - shopping related "best laptops under $1000" "compare iPhone vs Samsung" "top rated headphones 2026" # ❌ Bad - not shopping related "what is the capital of France" "how does photosynthesis work" ``` Bear in mind that the provider must support shopping cards (ChatGPT, Perplexity, Copilot, or AI Mode). ### Do you support Claude, Mistral, Meta AI, DeepSeek, or Amazon Rufus? No. See [Unsupported providers](#unsupported-providers) above for the current list and reasons. # Webhooks Source: https://cloro.dev/docs/guides/webhooks Receive async task results via webhooks: payload shape, retry behavior, HMAC signature verification in Node.js, Python, and Go, and troubleshooting tips. When you provide a `webhook.url` on an [async task](/docs/guides/making-requests/async), cloro sends an HTTP `POST` to your endpoint once the task reaches a terminal state — `COMPLETED` or `FAILED`. ## Enabling deliveries Include a `webhook.url` when you create the async task: ```json theme={null} { "taskType": "CHATGPT", "webhook": { "url": "https://your-app.com/webhook-handler" }, "payload": { "prompt": "...", "country": "US" } } ``` That's the whole opt-in. If you omit `webhook.url`, fall back to [polling](/docs/guides/making-requests/async#option-b-polling). Enable [signing](#verifying-deliveries) if your endpoint performs sensitive operations (charging, writing to your database) based on webhook content. ## Receiving deliveries Your endpoint receives a JSON body containing the task metadata, credit accounting, and the full provider response: ```json Webhook payload theme={null} { "task": { "id": "b27a21e1-7c39-4aa2-a347-23e828c426f9", "taskType": "CHATGPT", "status": "COMPLETED", "priority": 5, "createdAt": "2025-11-10T15:00:00.000Z", "latencyMs": 3420, "idempotencyKey": "your-custom-identifier-123" }, "credits": { "creditsToCharge": 10, "creditsCharged": 10 }, "response": { "model": "gpt-5-3-mini", "text": "The weather in New York is currently sunny...", "html": "https://cdn.cloro.dev/results/c45a5081-808d-4ed3-9c86-e4baf16c8ab8/page-1.html", "sources": [], "shoppingCards": [], "entities": [], "markdown": "The weather in New York is currently sunny...", "searchQueries": ["weather in New York"] } } ``` ### Responding to a webhook Respond with any `2xx` status code (typically `200 OK`) to acknowledge receipt. Anything else — non-`2xx`, TLS errors, timeouts — counts as a failed delivery and triggers a retry. ```javascript Node.js (Express) theme={null} app.post('/webhook-handler', (req, res) => { // Process the result asynchronously console.log(req.body); // Immediately acknowledge receipt res.status(200).send(); }); ``` ```python Python (Flask) theme={null} @app.route('/webhook-handler', methods=['POST']) def handle_webhook(): # Process the result asynchronously print(request.json) # Immediately acknowledge receipt return ('', 200) ``` ## Retries and deduplication cloro retries failed deliveries up to **5 attempts** with exponential backoff. If an attempt fails, the next one is scheduled for: * **Attempt 2:** \~2 minutes later * **Attempt 3:** \~4 minutes later * **Attempt 4:** \~8 minutes later * **Attempt 5:** \~16 minutes later The same logical task may therefore arrive at your endpoint multiple times. If you need exactly-once handling, deduplicate on the `task.id` field inside the payload. (Signed deliveries also carry an [`X-Cloro-Webhook-Id`](#what-we-send) header that's unique per attempt, but `task.id` is always available.) **Correlating webhooks to your original submissions**: set `idempotencyKey` to a unique string (e.g., your internal job ID) when you submit an async task. That same key is included in every webhook payload for the task under `task.idempotencyKey`, letting you match each callback to the original request without maintaining a separate taskId lookup table. ## Verifying deliveries **Anyone who can reach your endpoint could send a forged request that looks identical to a real cloro delivery** unless you verify the signature. Webhook signing is opt-in per organization. ### Enabling signing Enable signing from the [dashboard](https://dashboard.cloro.dev/webhooks). cloro generates a secret prefixed with `whsec_` and shows it to you **exactly once** — copy it immediately. It's the only thing your endpoint needs to verify signatures. Anyone with the signing secret can forge payloads that pass your verification check. Store it in a secret manager, not in source code, and rotate it from the dashboard if it ever leaks. ### What we send Every signed delivery includes three headers in addition to the standard `Content-Type: application/json`: | Header | Example | Purpose | | -------------------- | ---------------------------------------- | ------------------------------------------------------------ | | `X-Cloro-Timestamp` | `1748419200` | Unix timestamp (seconds) when cloro signed the delivery | | `X-Cloro-Signature` | `v1=ab12cd34...` | HMAC-SHA256 signature, prefixed with the scheme version | | `X-Cloro-Webhook-Id` | `b27a21e1-7c39-4aa2-a347-23e828c426f9-1` | Unique delivery ID (`-`) for deduplication | ### How the signature is computed ``` signed_payload = "" + "." + "" signature = HMAC-SHA256(your_signing_secret, signed_payload) ``` cloro hex-encodes the HMAC output and ships it as the `v1=` portion of `X-Cloro-Signature`. Your endpoint recomputes the same value and compares it to the header. The timestamp goes inside the signed payload so an attacker can't replay an intercepted webhook against you indefinitely — your endpoint can reject anything signed more than a few minutes ago. ### Verification #### Step 1 — capture the raw body The signature is computed over the **exact bytes** of the request body. If your web framework parses the JSON and then re-serializes it before you see it (Express's `express.json()` does this, as does Flask's `request.json` when the body type is detected as JSON), the bytes you check can differ from the bytes cloro signed — different float formatting, key ordering, or whitespace — and verification will silently fail. Capture the raw bytes first, and parse the JSON only after verification passes. #### Step 2 — verify the signature ```javascript Node.js theme={null} import crypto from "crypto"; import express from "express"; const app = express(); const SIGNING_SECRET = process.env.CLORO_WEBHOOK_SECRET; const TOLERANCE_SECONDS = 5 * 60; // reject anything older than 5 minutes app.post( "/webhooks/cloro", // IMPORTANT: raw() not json() — we need the exact bytes cloro signed. express.raw({ type: "application/json" }), (req, res) => { const timestamp = req.header("X-Cloro-Timestamp"); const signatureHeader = req.header("X-Cloro-Signature"); const rawBody = req.body.toString("utf8"); if (!timestamp || !signatureHeader) { return res.status(400).send("missing signature headers"); } // Replay protection const now = Math.floor(Date.now() / 1000); if (Math.abs(now - Number(timestamp)) > TOLERANCE_SECONDS) { return res.status(400).send("timestamp outside tolerance"); } // Compute the expected signature const expected = crypto .createHmac("sha256", SIGNING_SECRET) .update(`${timestamp}.${rawBody}`) .digest("hex"); const provided = signatureHeader.replace(/^v1=/, ""); if ( provided.length !== expected.length || !crypto.timingSafeEqual( Buffer.from(expected, "utf8"), Buffer.from(provided, "utf8") ) ) { return res.status(401).send("invalid signature"); } // Parse JSON only after verification has passed const payload = JSON.parse(rawBody); // …handle payload… res.status(200).send("ok"); } ); ``` ```python Python theme={null} import hashlib import hmac import os import time from flask import Flask, request, abort SIGNING_SECRET = os.environ["CLORO_WEBHOOK_SECRET"].encode() TOLERANCE_SECONDS = 5 * 60 # reject anything older than 5 minutes app = Flask(__name__) @app.route("/webhooks/cloro", methods=["POST"]) def cloro_webhook(): timestamp = request.headers.get("X-Cloro-Timestamp") signature_header = request.headers.get("X-Cloro-Signature", "") raw_body = request.get_data() # bytes, exactly as received if not timestamp or not signature_header: abort(400, "missing signature headers") # Replay protection if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS: abort(400, "timestamp outside tolerance") # Compute the expected signature signed_payload = f"{timestamp}.".encode() + raw_body expected = hmac.new(SIGNING_SECRET, signed_payload, hashlib.sha256).hexdigest() provided = signature_header.removeprefix("v1=") if not hmac.compare_digest(expected, provided): abort(401, "invalid signature") # Parse the JSON only after verification has passed payload = request.get_json(force=True) # …handle payload… return ("ok", 200) ``` ```go Go theme={null} package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "io" "net/http" "os" "strconv" "strings" "time" ) var ( signingSecret = []byte(os.Getenv("CLORO_WEBHOOK_SECRET")) toleranceSeconds = int64(5 * 60) ) func cloroWebhook(w http.ResponseWriter, r *http.Request) { timestamp := r.Header.Get("X-Cloro-Timestamp") signatureHeader := r.Header.Get("X-Cloro-Signature") rawBody, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "could not read body", http.StatusBadRequest) return } if timestamp == "" || signatureHeader == "" { http.Error(w, "missing signature headers", http.StatusBadRequest) return } // Replay protection tsInt, err := strconv.ParseInt(timestamp, 10, 64) if err != nil || abs(time.Now().Unix()-tsInt) > toleranceSeconds { http.Error(w, "timestamp outside tolerance", http.StatusBadRequest) return } // Compute the expected signature mac := hmac.New(sha256.New, signingSecret) mac.Write([]byte(timestamp + ".")) mac.Write(rawBody) expected := hex.EncodeToString(mac.Sum(nil)) provided := strings.TrimPrefix(signatureHeader, "v1=") if !hmac.Equal([]byte(expected), []byte(provided)) { http.Error(w, "invalid signature", http.StatusUnauthorized) return } // Parse JSON / handle payload only after verification has passed // … w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) } func abs(n int64) int64 { if n < 0 { return -n } return n } ``` The 5-minute tolerance is what we recommend — adjust if your endpoint sits behind slow networks or you want stricter replay protection. ### Common pitfalls * **String equality instead of constant-time compare** — a `==` comparison on the hex strings leaks information about the expected signature via timing differences. Use `crypto.timingSafeEqual` (Node), `hmac.compare_digest` (Python), or `hmac.Equal` (Go). * **Parsing the body before verifying** — serverless wrappers do this as often as Express does. Read the raw bytes first. * **No timestamp check** — without it, one intercepted delivery can be replayed against you indefinitely. * **Storing the secret in source code** — if it leaks, rotate it immediately from the dashboard. ## Disabling or rotating You can rotate or disable signing at any time from the [dashboard](https://dashboard.cloro.dev/webhooks). * **Rotating** generates a new secret immediately. Your existing receivers will reject signatures until you update them with the new value, so coordinate the rotation with your deploy. * **Disabling** stops sending the `X-Cloro-*` headers. Existing receivers that verify will start rejecting payloads until you remove the verification check on their side. ## Troubleshooting ### My webhook never arrived. What should I check? Work through these in order: 1. **Look up the task.** Call `GET /v1/async/task/{taskId}` — if the status is terminal, the result already exists and you can fetch it during the 24-hour retention window. 2. **Check the URL you submitted.** Typos, missing protocol, and non-public hostnames (e.g., `localhost`) will not deliver. 3. **Check your endpoint's response.** Non-`2xx` responses, TLS errors, and long timeouts can exhaust the [retry budget](#retries-and-deduplication). 4. **Don't assume order.** Webhooks for a batch arrive in completion order, not submission order. Poll `/v1/async/status` if you need a count of outstanding tasks. ## Need help? * Get help: paid plans via the in-dashboard support widget, free tier via the **Ask Assistant** button in these docs * Async requests: [Making requests → Asynchronous requests](/docs/guides/making-requests/async) * Async task reference: [`POST /v1/async/task`](/docs/api-reference/endpoint/create-async-task) * Authentication: [API keys & Bearer tokens](/docs/guides/authentication) # Welcome to cloro Source: https://cloro.dev/docs/index Extract structured data from ChatGPT, Perplexity, Microsoft Copilot, Google AI Mode, Google Search with AI Overview and other AI models cloro is an API platform for monitoring AI responses and search results across multiple providers. Get structured data from Google Search, ChatGPT, Perplexity, Copilot, and more through a unified API. ## Get started Make your first request Manage your API key Understand API structure Scale efficiently Explore endpoint documentation Monitor usage View providers and costs ## Why cloro? cloro returns AI responses as parsed markdown with their sources and citations. You can run the same request against multiple countries and regions to see how the answers change locally. It is a REST API, so everything comes back as structured JSON. ## Available endpoints | Endpoint | Best for | | ---------------------------------------------------------------- | ------------------------------- | | [ChatGPT](/docs/api-reference/endpoint/monitor-chatgpt) | E-commerce, shopping data | | [Copilot](/docs/api-reference/endpoint/monitor-copilot) | Microsoft ecosystem | | [Perplexity](/docs/api-reference/endpoint/monitor-perplexity) | Real-time research | | [Grok](/docs/api-reference/endpoint/monitor-grok) | Current events, news tracking | | [Google Search](/docs/api-reference/endpoint/monitor-google) | SEO monitoring, market research | | [Google News](/docs/api-reference/endpoint/monitor-google-news) | News monitoring, media tracking | | [Google Gemini](/docs/api-reference/endpoint/monitor-gemini) | General purpose, reasoning | | [Google AI Mode](/docs/api-reference/endpoint/monitor-aimode) | General knowledge | | [Google AI Overview](/docs/api-reference/endpoint/google/ai-overview) | Search analysis | See [Providers](/docs/guides/providers) for endpoint costs. ## Use these docs with AI coding assistants These docs are published in `llms.txt` format for AI coding assistants like Claude Code, Cursor, and GitHub Copilot. Paste one of these URLs (or the file contents) into your assistant's context so it can reason about cloro endpoints, parameters, and response shapes without further lookups: * **[llms.txt](https://cloro.dev/docs/llms.txt)** — index of every docs page with one-line descriptions * **[llms-full.txt](https://cloro.dev/docs/llms-full.txt)** — full text of every docs page concatenated into a single file ## Quick links * **[Dashboard](https://dashboard.cloro.dev)** - Manage API keys and billing * **[Status Page](https://status.cloro.dev)** - Check API status * **[Community](https://www.reddit.com/r/cloroapi/)** - Get help from the community * **[Ask Assistant](https://cloro.dev/docs/?assistant)** - Ask the docs AI assistant # LangChain Source: https://cloro.dev/docs/integrations/langchain Use the langchain-cloro Python package to give LangChain agents ready-made tools for Google Search, ChatGPT, Gemini, Perplexity, and Copilot. The [`langchain-cloro`](https://pypi.org/project/langchain-cloro/) Python package wraps cloro's [sync monitor endpoints](/docs/guides/making-requests/sync) as LangChain tools, so your agents can query AI engines and Google Search and reason over structured results with sources. It's listed in the [LangChain integrations docs](https://docs.langchain.com/oss/python/integrations/tools/cloro), and the source lives in the [langchain-cloro repository](https://github.com/cloro-dev/langchain-cloro). ## Prerequisites * Python with [LangChain](https://python.langchain.com) installed * A cloro API key from the [dashboard](https://dashboard.cloro.dev) (see [Authentication](/docs/guides/authentication)) ## Install the package ```bash theme={null} pip install langchain-cloro ``` ## Configure your API key Set the key as an environment variable: ```bash theme={null} export CLORO_API_KEY="YOUR_API_KEY" ``` Or pass it directly when initializing a tool: ```python theme={null} from langchain_cloro import CloroGoogleSearch tool = CloroGoogleSearch(cloro_api_key="YOUR_API_KEY") ``` ## Available tools | Tool | Endpoint | | ------------------- | --------------------------------------------------------------------------- | | `CloroGoogleSearch` | [`POST /v1/monitor/google`](/docs/api-reference/endpoint/monitor-google) | | `CloroChatGPT` | [`POST /v1/monitor/chatgpt`](/docs/api-reference/endpoint/monitor-chatgpt) | | `CloroGemini` | [`POST /v1/monitor/gemini`](/docs/api-reference/endpoint/monitor-gemini) | | `CloroPerplexity` | [`POST /v1/monitor/perplexity`](/docs/api-reference/endpoint/monitor-perplexity) | | `CloroGrok` | [`POST /v1/monitor/grok`](/docs/api-reference/endpoint/monitor-grok) | | `CloroCopilot` | [`POST /v1/monitor/copilot`](/docs/api-reference/endpoint/monitor-copilot) | Every tool accepts `country` (ISO 3166-1 alpha-2, defaults to `"US"`), `include_html`, and `include_markdown`. `CloroGoogleSearch` takes a `query` plus `device` (`"desktop"` or `"mobile"`), `pages` (1–10), `include_aioverview`, and `aioverview_markdown`; the assistant tools take a `prompt`, and `CloroChatGPT` additionally supports `include_raw_response` and `include_search_queries` (query fan-out). The package also exports `get_countries()` for the [supported countries](/docs/api-reference/endpoint/countries), optionally filtered by provider. CloroGrok is still exported, but ## Invoke a tool directly ```python theme={null} from langchain_cloro import CloroGoogleSearch tool = CloroGoogleSearch() results = tool.invoke({ "query": "best laptops for programming", "include_aioverview": True, "aioverview_markdown": True, }) ``` ## Use with an agent ```python theme={null} from langchain.agents import initialize_agent, AgentType from langchain_openai import OpenAI from langchain_cloro import CloroGoogleSearch, CloroChatGPT llm = OpenAI(temperature=0) tools = [CloroGoogleSearch(), CloroChatGPT()] agent = initialize_agent( tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True, ) agent.run("What are the latest developments in AI?") ``` Tool calls run synchronously and count against your [concurrency limits](/docs/guides/concurrency). For large batches (tracking many prompts across engines), use the [async API](/docs/guides/making-requests/async) directly instead of looping tool calls. Each call consumes credits at the standard per-provider rates — see [Providers](/docs/guides/providers) and [Billing & credits](/docs/guides/billing). # MCP server: give any agent cloro's monitoring tools Source: https://cloro.dev/docs/integrations/mcp Connect Claude, Cursor, or any MCP client to cloro's hosted MCP server and query ChatGPT, Perplexity, Gemini, Copilot, and Google Search as agent tools. The cloro [MCP](https://modelcontextprotocol.io) server exposes cloro's [sync monitor endpoints](/docs/guides/making-requests/sync) as tools for any MCP-capable client: [Claude](https://claude.ai), [Claude Code](https://docs.claude.com/en/docs/claude-code), [Cursor](https://cursor.com), or your own agent. Ask your agent to "check whether ChatGPT recommends us for *best SERP API*" and it calls the cloro API and reasons over the parsed result: the answer text and the sources it cited. The server is a thin wrapper over `api.cloro.dev` holding no business logic and no secrets: each request forwards your own cloro API key as a Bearer token, so the API enforces authentication, credit billing, rate limiting, and concurrency limits. Tool input schemas are the same schemas the API validates with, so the MCP contract can't drift from the [API reference](/docs/api-reference/introduction). ## Prerequisites * An MCP-capable client (Claude Desktop, Claude Code, Cursor, or any other) * A cloro API key from the [dashboard](https://dashboard.cloro.dev) (see [Authentication](/docs/guides/authentication)) ## Connect your client The hosted server speaks Streamable HTTP. Pass your API key in the URL path so it works even in clients that can't set custom headers: ```json theme={null} { "mcpServers": { "cloro": { "type": "http", "url": "https://mcp.cloro.dev/YOUR_CLORO_API_KEY/mcp" } } } ``` If your client can set headers, you can instead point it at `https://mcp.cloro.dev/mcp` and send your key as `Authorization: Bearer YOUR_CLORO_API_KEY`. In Claude Desktop and Cursor, add this block to the app's MCP settings (Settings → Developer / MCP). In Claude Code, run `claude mcp add --transport http cloro https://mcp.cloro.dev/YOUR_CLORO_API_KEY/mcp`. ## Available tools | Tool | Endpoint | | ----------------------- | ----------------------------------------------------------------------------- | | `scrape_chatgpt` | [`POST /v1/monitor/chatgpt`](/docs/api-reference/endpoint/monitor-chatgpt) | | `scrape_gemini` | [`POST /v1/monitor/gemini`](/docs/api-reference/endpoint/monitor-gemini) | | `scrape_copilot` | [`POST /v1/monitor/copilot`](/docs/api-reference/endpoint/monitor-copilot) | | `scrape_perplexity` | [`POST /v1/monitor/perplexity`](/docs/api-reference/endpoint/monitor-perplexity) | | `scrape_grok` | [`POST /v1/monitor/grok`](/docs/api-reference/endpoint/monitor-grok) | | `scrape_google_ai_mode` | [`POST /v1/monitor/aimode`](/docs/api-reference/endpoint/monitor-aimode) | | `scrape_google` | [`POST /v1/monitor/google`](/docs/api-reference/endpoint/monitor-google) | | `scrape_google_news` | [`POST /v1/monitor/google/news`](/docs/api-reference/endpoint/monitor-google-news) | | `list_countries` | [`GET /v1/countries`](/docs/api-reference/endpoint/countries) | | `list_states` | [`GET /v1/states`](/docs/api-reference/endpoint/states) | The assistant tools (`scrape_chatgpt`, `scrape_gemini`, `scrape_copilot`, `scrape_perplexity`, `scrape_grok`) require a `prompt` and a `country` (ISO 3166-1 alpha-2), and accept an optional `state` for US state-level targeting. `scrape_google_ai_mode` also takes `prompt` and `country`, but targets sub-country with `location` or `uule` rather than `state`, and accepts `device`. `scrape_google_news` requires a `query` and a `country`, and accepts `device` and `pages`. `scrape_google` runs in one of two modes: a `query` with a `country` (plus optional `location` or `uule`, `device`, and `pages`), or a complete `google.com/search` `url`, which carries the query, location, and pagination itself and lets `country` be derived from its `gl` parameter. It also accepts `include.aioverview` to extract Google's AI Overview and `include.paaAioverview` to hydrate the AI answers in People Also Ask. Every tool accepts an optional `include` object to add heavier payload fields; leave it unset for the leanest response. Use `list_countries` (optionally filtered by model) and `list_states` (with a `country`) to discover valid geo-targeting codes. `scrape_grok` is registered, but Grok is [temporarily unavailable](/docs/guides/providers). Calls to it fail until the provider is restored. ## Credits and limits Each tool call runs synchronously, consumes credits at the standard per-provider rates (see [Providers](/docs/guides/providers) and [Billing & credits](/docs/guides/billing)), and counts against your [concurrency limits](/docs/guides/concurrency). For large batches, such as tracking many prompts across engines, use the [async API](/docs/guides/making-requests/async) directly instead of looping tool calls. ## Example prompts * "Ask ChatGPT what the best SERP APIs are and list which vendors it cites." * "Compare how Perplexity and Copilot answer 'best CRM for startups'. Which sources do they cite?" * "Does Google's AI Overview for 'ai visibility tracking' mention cloro? Check in the US and Germany." * "Pull the latest Google News articles about our brand." # n8n integration — query AI engines from workflows Source: https://cloro.dev/docs/integrations/n8n Install the n8n-nodes-cloro community node and query ChatGPT, Perplexity, Gemini, Copilot, Google AI Mode, Google Search, and Google News from any n8n workflow. The cloro community node ([`n8n-nodes-cloro`](https://www.npmjs.com/package/n8n-nodes-cloro)) adds a **cloro** node to [n8n](https://n8n.io) that calls the [sync monitor endpoints](/docs/guides/making-requests/sync) and returns parsed, structured results into your workflow. It's listed in the [n8n integrations directory](https://n8n.io/integrations/cloro/). ## Prerequisites * A running n8n instance that allows community nodes (see the [n8n installation guide](https://docs.n8n.io/integrations/community-nodes/installation/)) * A cloro API key from the [dashboard](https://dashboard.cloro.dev) (see [Authentication](/docs/guides/authentication)) ## Install the node In the n8n UI, go to **Settings → Community nodes → Install** and search for `n8n-nodes-cloro`. On a self-hosted instance you can also install it from the n8n installation directory: ```bash theme={null} npm install n8n-nodes-cloro ``` ## Configure credentials Create a new **cloro API** credential and paste your API key. The node sends it on every request as a `Bearer` token in the `Authorization` header. ## Resources and operations Since node version 0.2.0, each AI engine is its own resource, mapping 1:1 to the matching sync monitor endpoint: | Resource | Operation | Endpoint | | ------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------- | | ChatGPT, Google AI Mode, Google Gemini, Google News, Google Search, Grok, Microsoft Copilot, Perplexity | Monitor | [`POST /v1/monitor/`](/docs/guides/making-requests/sync) | | Country | Get Many | [`GET /v1/countries`](/docs/api-reference/endpoint/countries) | | Task | Get Status | [`GET /v1/tasks/{taskId}`](/docs/api-reference/endpoint/get-task-status) | Workflows built with earlier node versions keep the original single Monitor operation with a provider dropdown and continue to run unchanged. Monitor fields by resource: * **Google Search** — query, country, city, device (desktop/mobile), number of pages, include AI Overview, include HTML, include markdown * **Google News** — query, country (required), device (desktop/mobile), number of pages (1-10), include HTML * **ChatGPT** — prompt, country, include HTML, include markdown, include raw response, include search queries (query fan-out) * **Google AI Mode, Google Gemini, Grok, Microsoft Copilot, Perplexity** — prompt, country, include HTML, include markdown Grok is still selectable, but **Country: Get Many** returns the supported countries, optionally filtered by engine — useful for validating country codes upstream in a workflow. **Task: Get Status** checks an [async task](/docs/guides/making-requests/async) by its task ID, if you create tasks elsewhere and poll from n8n. ## Example: monitoring Google Search results ```text theme={null} Node: cloro Resource: Google Search Operation: Monitor Query: "best coffee shops in New York" Country: United States Device: Desktop Pages: 2 Include AI Overview: true Include Markdown: true ``` The node returns the same JSON as the [Google Search endpoint](/docs/api-reference/endpoint/monitor-google): organic results, People Also Ask, related searches, and the AI Overview when requested. Monitor calls run synchronously and count against your [concurrency limits](/docs/guides/concurrency). Each call consumes credits at the standard per-provider rates — see [Providers](/docs/guides/providers) and [Billing & credits](/docs/guides/billing). # OpenClaw Source: https://cloro.dev/docs/integrations/openclaw Install the cloro plugin for OpenClaw and give your agent tools to monitor ChatGPT, Perplexity, Gemini, Copilot, Google AI Mode, Google Search, and Google News. The cloro plugin for [OpenClaw](https://openclaw.ai) is a thin client over cloro's [sync monitor endpoints](/docs/guides/making-requests/sync) — the same `POST /v1/monitor/*` requests documented in the [API reference](/docs/api-reference/introduction) — exposed as agent tools. Ask your OpenClaw agent to "check whether Google's AI Overview for 'best ai seo tools' mentions us" and it calls the cloro API and reasons over the parsed JSON. ## Prerequisites * A running [OpenClaw](https://docs.openclaw.ai) gateway * A cloro API key from the [dashboard](https://dashboard.cloro.dev) (see [Authentication](/docs/guides/authentication)) ## Install the plugin Install from the [cloro OpenClaw plugin repository](https://github.com/cloro-dev/openclaw-plugin) with the OpenClaw CLI: ```bash theme={null} openclaw plugins install git:github.com/cloro-dev/openclaw-plugin openclaw plugins enable cloro openclaw gateway restart ``` ## Configure your API key Set the key as an environment variable available to the OpenClaw gateway: ```bash theme={null} export CLORO_API_KEY="YOUR_API_KEY" ``` Or add it to `openclaw.json` under the plugin's config block: ```json theme={null} { "plugins": { "entries": { "cloro": { "enabled": true, "config": { "apiKey": "YOUR_API_KEY", "defaultCountry": "us" } } } } } ``` Optional config: `baseUrl` (defaults to `https://api.cloro.dev`), `defaultCountry` (used when a tool call omits `country`, defaults to `us`), and `timeoutSeconds` (defaults to `300` — monitor requests drive real provider sessions and can take a while). ## Available tools | Tool | Endpoint | | ------------------- | ----------------------------------------------------------------------------- | | `cloro_chatgpt` | [`POST /v1/monitor/chatgpt`](/docs/api-reference/endpoint/monitor-chatgpt) | | `cloro_perplexity` | [`POST /v1/monitor/perplexity`](/docs/api-reference/endpoint/monitor-perplexity) | | `cloro_gemini` | [`POST /v1/monitor/gemini`](/docs/api-reference/endpoint/monitor-gemini) | | `cloro_copilot` | [`POST /v1/monitor/copilot`](/docs/api-reference/endpoint/monitor-copilot) | | `cloro_aimode` | [`POST /v1/monitor/aimode`](/docs/api-reference/endpoint/monitor-aimode) | | `cloro_google` | [`POST /v1/monitor/google`](/docs/api-reference/endpoint/monitor-google) | | `cloro_google_news` | [`POST /v1/monitor/google/news`](/docs/api-reference/endpoint/monitor-google-news) | Assistant tools take a `prompt` with optional `country` and `state` (US state targeting). `cloro_google` additionally supports `location`, `device`, `pages`, and an `aiOverview` flag that includes Google's AI Overview as markdown with sources. There is no Grok tool because Each call consumes credits at the standard per-provider rates — see [Providers](/docs/guides/providers) and [Billing & credits](/docs/guides/billing). ## Alternative: connect the cloro MCP server OpenClaw can also connect to cloro's hosted [MCP server](/docs/integrations/mcp) directly for the same monitor tools. Pass your cloro API key in the URL path: ```bash theme={null} openclaw mcp add cloro \ --url https://mcp.cloro.dev/YOUR_CLORO_API_KEY/mcp \ --transport streamable-http ``` Or in `openclaw.json`: ```json theme={null} { "mcp": { "servers": { "cloro": { "url": "https://mcp.cloro.dev/YOUR_CLORO_API_KEY/mcp", "transport": "streamable-http" } } } } ``` The plugin remains the better fit for OpenClaw workflows — defaults and API key managed in one config block — but the MCP route works with any MCP client, and its [guide](/docs/integrations/mcp) lists the full tool set. ## Example prompts * "Ask ChatGPT what the best SERP APIs are and list which vendors it cites." * "Compare how Perplexity and Copilot answer 'best CRM for startups' — which sources do they cite?" * "Does Google's AI Overview for 'ai visibility tracking' mention cloro? Check in the US and Germany." * "Pull the latest Google News articles about our brand." Tool calls run synchronously and count against your [concurrency limits](/docs/guides/concurrency). For large batches (tracking many prompts across engines), use the [async API](/docs/guides/making-requests/async) directly instead of looping tool calls. # Python SDK Source: https://cloro.dev/docs/integrations/python Official cloro Python SDK reference: install, authenticate, and call Google Search, ChatGPT, Gemini, Perplexity, Copilot, Grok, and AI Mode. The [`cloro`](https://pypi.org/project/cloro/) Python package is the official SDK for the cloro API — one typed client for Google Search and every AI answer engine, where each call is a single authenticated request that returns structured JSON with sources. The source lives in the [cloro-python repository](https://github.com/cloro-dev/cloro-python). ## Prerequisites * Python 3.8 or newer * A cloro API key from the [dashboard](https://dashboard.cloro.dev) (see [Authentication](/docs/guides/authentication)) ## Install the package ```bash theme={null} pip install cloro ``` ## Configure your API key The client reads `CLORO_API_KEY` from the environment automatically: ```bash theme={null} export CLORO_API_KEY="YOUR_API_KEY" ``` Or pass it to the constructor: ```python theme={null} from cloro import Cloro client = Cloro(api_key="YOUR_API_KEY") ``` ## Quickstart ```python theme={null} from cloro import Cloro client = Cloro() # reads CLORO_API_KEY res = client.monitor.chatgpt( prompt="What do you know about Acme Corp?", country="US", include={"markdown": True}, ) print(res["result"]["text"]) for source in res["result"]["sources"]: print(source["position"], source["url"], source["label"]) ``` Every call returns the `{"success": ..., "result": {...}}` envelope. Pass `include={...}` to request extra formats (`markdown`, `html`, `searchQueries`, `shopping`, and more, depending on the engine). ## Every engine, one client AI engines take a `prompt`; Google Search and Google News take a `query`. Each method is a thin wrapper over one endpoint. | Method | Endpoint | | -------------------------------------------- | ----------------------------------------------------------------------------- | | `client.monitor.google(query, country)` | [`POST /v1/monitor/google`](/docs/api-reference/endpoint/monitor-google) | | `client.monitor.chatgpt(prompt, country)` | [`POST /v1/monitor/chatgpt`](/docs/api-reference/endpoint/monitor-chatgpt) | | `client.monitor.gemini(prompt, country)` | [`POST /v1/monitor/gemini`](/docs/api-reference/endpoint/monitor-gemini) | | `client.monitor.perplexity(prompt, country)` | [`POST /v1/monitor/perplexity`](/docs/api-reference/endpoint/monitor-perplexity) | | `client.monitor.copilot(prompt, country)` | [`POST /v1/monitor/copilot`](/docs/api-reference/endpoint/monitor-copilot) | | `client.monitor.grok(prompt, country)` | [`POST /v1/monitor/grok`](/docs/api-reference/endpoint/monitor-grok) | | `client.monitor.aimode(prompt, country)` | [`POST /v1/monitor/aimode`](/docs/api-reference/endpoint/monitor-aimode) | | `client.monitor.google_news(query, country)` | [`POST /v1/monitor/google/news`](/docs/api-reference/endpoint/monitor-google-news) | `client.monitor.google` also accepts `location`, `uule`, `device`, and `pages` (1–10). The client exposes `client.countries()` and `client.states()` for the [supported countries](/docs/api-reference/endpoint/countries) and states. client.monitor.grok is available, but ## Async task queue For large batches, don't loop synchronous calls — enqueue tasks and poll them. `create_batch` submits up to 500 tasks in one request; `wait` polls a task to completion with interval backoff. See [Async requests](/docs/guides/making-requests/async). ```python theme={null} from cloro import Cloro client = Cloro() KEYWORDS = ["best running shoes", "trail running shoes", "waterproof running shoes"] # Enqueue up to 500 tasks in a single call. results = client.async_tasks.create_batch( [{"task_type": "GOOGLE", "payload": {"query": kw, "country": "US"}} for kw in KEYWORDS] ) for item in results: if item["success"]: done = client.async_tasks.wait(item["task"]["id"]) organic = done["response"]["result"]["organicResults"] if organic: print(item["task"]["id"], "→", organic[0]["link"]) else: print(item["task"]["id"], "→ no results") else: print("failed:", item["error"]["message"]) ``` For a single task, `client.async_tasks.run(task_type=..., payload=...)` creates it and blocks until it completes. Valid `task_type` values: `CHATGPT`, `GEMINI`, `PERPLEXITY`, `COPILOT`, `GROK`, `AIMODE`, `GOOGLE`, `GOOGLE_NEWS`. ## Reliability and configuration The client retries timeouts, connection errors, and `429`/`5xx` responses with exponential backoff — tune it with `Cloro(max_retries=2, timeout=60.0)`. Cap your own concurrency to your plan's limit (see [Concurrency](/docs/guides/concurrency)), read the key from `CLORO_API_KEY` rather than hardcoding it, and access response fields with `.get()` since shapes vary by query (a Google result with no AI Overview omits `aioverview`). ## Error handling Every error subclasses `CloroError`, so one `except CloroError` catches everything. HTTP failures map to status-specific types: ```python theme={null} from cloro import Cloro, AuthenticationError, RateLimitError, CloroError client = Cloro() try: res = client.monitor.chatgpt(prompt="...", country="US") except AuthenticationError: ... # 401 — bad or missing API key except RateLimitError: ... # 429 — the client already retried; back off further if it persists except CloroError as exc: ... # everything else ``` | Exception | Meaning | | -------------------------------------- | ---------------------------------------------------------------- | | `AuthenticationError` | `401` — missing or invalid API key | | `BadRequestError` | `400` — malformed request or failed validation | | `PermissionDeniedError` | `403` — key not allowed for this action, or insufficient credits | | `NotFoundError` | `404` — resource does not exist | | `ConflictError` | `409` — conflicts with current state (e.g. concurrency limit) | | `RateLimitError` | `429` — too many requests | | `InternalServerError` | `5xx` — the API failed to process the request | | `APITimeoutError` | request timed out before a response | | `TaskFailedError` / `TaskTimeoutError` | async task failed, or did not finish within the poll timeout | ## Next steps * [Python API guide](https://cloro.dev/integrations/python/) — the landing-page walkthrough with recipes, pricing, and production patterns * [TypeScript SDK](/docs/integrations/typescript) — the same client surface in TypeScript/Node * [Python web scraping pillar](https://cloro.dev/blog/web_scraping_with_python/) — Python data-extraction fundamentals * [Google SERP API](https://cloro.dev/serp-api/) — the SERP endpoint this SDK wraps * [Making requests](/docs/guides/making-requests/sync) — the raw sync and [async](/docs/guides/making-requests/async) HTTP contracts * [Providers](/docs/guides/providers) and [Billing & credits](/docs/guides/billing) — per-engine credit costs * [API reference](/docs/api-reference/endpoint/monitor-google) — full request and response schemas * [cloro-python on GitHub](https://github.com/cloro-dev/cloro-python) and [on PyPI](https://pypi.org/project/cloro/) # TypeScript SDK Source: https://cloro.dev/docs/integrations/typescript Official cloro TypeScript and Node SDK reference: install, authenticate, and call Google Search, ChatGPT, Gemini, Perplexity, Copilot, Grok, and AI Mode. The [`@cloro-dev/cloro`](https://www.npmjs.com/package/@cloro-dev/cloro) npm package is the official TypeScript/Node SDK for the cloro API — one typed client for Google Search and every AI answer engine, where each call is a single authenticated request that returns structured JSON with sources. It ships ESM and CommonJS builds with bundled type declarations; the source lives in the [cloro-node repository](https://github.com/cloro-dev/cloro-node). ## Prerequisites * Node 18 or newer (uses the built-in `fetch`) * A cloro API key from the [dashboard](https://dashboard.cloro.dev) (see [Authentication](/docs/guides/authentication)) ## Install the package ```bash theme={null} npm install @cloro-dev/cloro ``` ## Configure your API key The client reads `CLORO_API_KEY` from the environment automatically: ```bash theme={null} export CLORO_API_KEY="YOUR_API_KEY" ``` Or pass it to the constructor: ```ts theme={null} import { Cloro } from "@cloro-dev/cloro"; const client = new Cloro({ apiKey: "YOUR_API_KEY" }); ``` ## Quickstart ```ts theme={null} import { Cloro } from "@cloro-dev/cloro"; const client = new Cloro(); // reads CLORO_API_KEY const res = await client.monitor.chatgpt({ prompt: "What do you know about Acme Corp?", country: "US", include: { markdown: true }, }); console.log(res.result.text); for (const source of res.result.sources) { console.log(source.position, source.url, source.label); } ``` Every call resolves with the `{ success, result }` envelope. Pass `include: {...}` to request extra formats (`markdown`, `html`, `searchQueries`, `shopping`, and more, depending on the engine). ## Every engine, one client AI engines take a `prompt`; Google Search and Google News take a `query`. Each method is a thin wrapper over one endpoint. | Method | Endpoint | | ------------------------------------------------ | ----------------------------------------------------------------------------- | | `client.monitor.google({ query, country })` | [`POST /v1/monitor/google`](/docs/api-reference/endpoint/monitor-google) | | `client.monitor.chatgpt({ prompt, country })` | [`POST /v1/monitor/chatgpt`](/docs/api-reference/endpoint/monitor-chatgpt) | | `client.monitor.gemini({ prompt, country })` | [`POST /v1/monitor/gemini`](/docs/api-reference/endpoint/monitor-gemini) | | `client.monitor.perplexity({ prompt, country })` | [`POST /v1/monitor/perplexity`](/docs/api-reference/endpoint/monitor-perplexity) | | `client.monitor.copilot({ prompt, country })` | [`POST /v1/monitor/copilot`](/docs/api-reference/endpoint/monitor-copilot) | | `client.monitor.grok({ prompt, country })` | [`POST /v1/monitor/grok`](/docs/api-reference/endpoint/monitor-grok) | | `client.monitor.aimode({ prompt, country })` | [`POST /v1/monitor/aimode`](/docs/api-reference/endpoint/monitor-aimode) | | `client.monitor.googleNews({ query, country })` | [`POST /v1/monitor/google/news`](/docs/api-reference/endpoint/monitor-google-news) | `client.monitor.google` also accepts `location`, `uule`, `device`, and `pages` (1–10). The client exposes `client.countries()` and `client.states()` for the [supported countries](/docs/api-reference/endpoint/countries) and states. client.monitor.grok is available, but ## Async task queue For large batches, don't loop synchronous calls — enqueue tasks and poll them. `createBatch` submits up to 500 tasks in one request; `wait` polls a task to completion with interval backoff. See [Async requests](/docs/guides/making-requests/async). ```ts theme={null} import { Cloro } from "@cloro-dev/cloro"; const client = new Cloro(); const KEYWORDS = ["best running shoes", "trail running shoes", "waterproof running shoes"]; // Enqueue up to 500 tasks in a single call. const results = await client.asyncTasks.createBatch( KEYWORDS.map((kw) => ({ taskType: "GOOGLE", payload: { query: kw, country: "US" } })), ); for (const item of results) { if (item.success) { const done = await client.asyncTasks.wait(item.task.id); const organic = done.response.result.organicResults; if (organic?.length) { console.log(item.task.id, "→", organic[0].link); } else { console.log(item.task.id, "→ no results"); } } else { console.log("failed:", item.error.message); } } ``` For a single task, `client.asyncTasks.run({ taskType, payload })` creates it and resolves once it completes. Valid `taskType` values: `CHATGPT`, `GEMINI`, `PERPLEXITY`, `COPILOT`, `GROK`, `AIMODE`, `GOOGLE`, `GOOGLE_NEWS`. ## Reliability and configuration The client retries timeouts, connection errors, and `429`/`5xx` responses with exponential backoff — tune it with `new Cloro({ maxRetries: 2, timeout: 60000 })` (timeout in milliseconds). Cap your own concurrency to your plan's limit (see [Concurrency](/docs/guides/concurrency)), read the key from `CLORO_API_KEY` rather than hardcoding it, and access response fields with optional chaining (`?.`) since shapes vary by query (a Google result with no AI Overview omits `aioverview`). ## Error handling Every error subclasses `CloroError`, so one `instanceof CloroError` check catches everything. HTTP failures map to status-specific types: ```ts theme={null} import { Cloro, AuthenticationError, RateLimitError, CloroError } from "@cloro-dev/cloro"; const client = new Cloro(); try { const res = await client.monitor.chatgpt({ prompt: "...", country: "US" }); } catch (err) { if (err instanceof AuthenticationError) { // 401 — bad or missing API key } else if (err instanceof RateLimitError) { // 429 — the client already retried; back off further if it persists } else if (err instanceof CloroError) { // everything else } else { throw err; } } ``` | Exception | Meaning | | -------------------------------------- | ---------------------------------------------------------------- | | `AuthenticationError` | `401` — missing or invalid API key | | `BadRequestError` | `400` — malformed request or failed validation | | `PermissionDeniedError` | `403` — key not allowed for this action, or insufficient credits | | `NotFoundError` | `404` — resource does not exist | | `ConflictError` | `409` — conflicts with current state (e.g. concurrency limit) | | `RateLimitError` | `429` — too many requests | | `InternalServerError` | `5xx` — the API failed to process the request | | `APITimeoutError` | request timed out before a response | | `TaskFailedError` / `TaskTimeoutError` | async task failed, or did not finish within the poll timeout | ## Next steps * [JavaScript & Node API guide](https://cloro.dev/integrations/javascript/) — the landing-page walkthrough with recipes, pricing, and production patterns * [Python SDK](/docs/integrations/python) — the same client surface in Python * [Google SERP API](https://cloro.dev/serp-api/) — the SERP endpoint this SDK wraps * [Making requests](/docs/guides/making-requests/sync) — the raw sync and [async](/docs/guides/making-requests/async) HTTP contracts * [Providers](/docs/guides/providers) and [Billing & credits](/docs/guides/billing) — per-engine credit costs * [API reference](/docs/api-reference/endpoint/monitor-google) — full request and response schemas * [cloro-node on GitHub](https://github.com/cloro-dev/cloro-node) and [on npm](https://www.npmjs.com/package/@cloro-dev/cloro) # Zapier integration — connect cloro to 8,000+ apps Source: https://cloro.dev/docs/integrations/zapier Connect cloro to 8,000+ apps with Zapier — trigger AI-engine and Google Search checks from any Zap and send parsed results to Sheets, Slack, or your CRM. The [cloro app for Zapier](https://zapier.com/apps/cloro/integrations) exposes a single **Submit Prompt** action that sends a prompt to an AI or search engine and returns the parsed, structured result to your Zap. The action creates an [async task](/docs/api-reference/endpoint/create-async-task) with a Zapier callback URL as the [webhook](/docs/guides/webhooks) target, so the Zap step pauses until cloro delivers the result and long-running monitor requests never hit Zapier's timeout limits. ## Prerequisites * A [Zapier](https://zapier.com) account * A cloro API key from the [dashboard](https://dashboard.cloro.dev) (see [Authentication](/docs/guides/authentication)) ## Set up the action 1. In the Zap editor, add a step and search for **cloro**. 2. Select the **Submit Prompt** action. 3. Connect your cloro account by pasting your API key when prompted. 4. Pick the engine in the **Tool** dropdown and fill in the input fields below. ## Supported engines | Tool | Result schema | | ------------------ | ---------------------------------------------------------------------- | | ChatGPT | [`/v1/monitor/chatgpt`](/docs/api-reference/endpoint/monitor-chatgpt) | | Perplexity | [`/v1/monitor/perplexity`](/docs/api-reference/endpoint/monitor-perplexity) | | Grok | [`/v1/monitor/grok`](/docs/api-reference/endpoint/monitor-grok) | | Gemini | [`/v1/monitor/gemini`](/docs/api-reference/endpoint/monitor-gemini) | | Copilot | [`/v1/monitor/copilot`](/docs/api-reference/endpoint/monitor-copilot) | | Google Search | [`/v1/monitor/google`](/docs/api-reference/endpoint/monitor-google) | | Google AI Mode | [`/v1/monitor/aimode`](/docs/api-reference/endpoint/monitor-aimode) | | Google AI Overview | [AI Overview](/docs/api-reference/endpoint/google/ai-overview) | The Grok tool is still listed in the dropdown, but ## Input fields | Field | Description | | ---------------- | ------------------------------------------------------------------------------------------------ | | Tool | The AI or search engine to query (required). | | Prompt | The prompt to submit, 1–10,000 characters (required). Used as the search query for Google tools. | | Country | ISO 3166-1 alpha-2 country code (uppercase) for localized results (required). | | Device | Google tools only — `desktop`, `mobile`, or `tablet`. Defaults to `desktop`. | | Number of Pages | Google tools only — number of result pages to fetch. Defaults to 1. | | Include HTML | Include the raw HTML output in the response. Defaults to false. | | Include Markdown | Include the markdown output in the response. Defaults to true. | ## Output fields Every run returns the task metadata (`taskId`, `status`, `success`, `completedAt`) plus the parsed result: `text`, `markdown`, `html`, `model`, `sources` (position, URL, label, description), `entities`, `searchQueries`, and `shoppingCards` when available — all mappable into later Zap steps. ## Example Zaps * **Schedule by Zapier → cloro → Google Sheets**: run a fixed set of brand prompts daily and append the answers and cited sources to a tracking sheet. * **Google Forms → cloro → Slack**: let teammates submit a prompt from a form and post the parsed answer to a channel. * **Schedule by Zapier → cloro (Google AI Overview) → Email**: watch whether the AI Overview for a target query cites your domain and get notified of the result. Each run consumes credits at the standard per-provider rates — see [Providers](/docs/guides/providers) and [Billing & credits](/docs/guides/billing). # Quickstart: make your first cloro API request Source: https://cloro.dev/docs/introduction/quickstart Get started with the cloro API in minutes. Learn how to get your API key, make your first monitoring request, and understand the response structure. This guide takes you from an API key to your first monitoring response. ## 1. Get your API key You can find your API key in your [dashboard](https://dashboard.cloro.dev/). New accounts include free credits on sign-up so you can test the API before subscribing to a paid plan. ## 2. Make your first request All cloro monitoring endpoints are **POST** endpoints that expect a JSON body and a bearer-token `Authorization` header. You cannot call them by pasting a URL into a browser address bar (that would send a `GET` with no body). Use any of these instead: * **curl** in a terminal (shown below) * A GUI HTTP client such as **Postman**, **Insomnia**, or **Bruno** * Code — see the [Python](/docs/integrations/python) and [TypeScript](/docs/integrations/typescript) integration guides ```bash curl theme={null} curl -X POST "https://api.cloro.dev/v1/monitor/chatgpt" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "What do you know about Acme Corp?", "country": "US" }' ``` ```text Postman theme={null} Method: POST URL: https://api.cloro.dev/v1/monitor/chatgpt Headers: Authorization: Bearer YOUR_API_KEY Content-Type: application/json Body (raw JSON): { "prompt": "What do you know about Acme Corp?", "country": "US" } ``` ## 3. See the response ```json theme={null} { "success": true, "result": { "text": "Acme Corp is a fictional corporation that appears in many cartoons.", "sources": [] } } ``` ## Use these docs with AI coding assistants If you'd rather have Claude Code, Cursor, GitHub Copilot, or another AI assistant write the request for you, give it these docs as context first — see [Use these docs with AI coding assistants](/docs/#use-these-docs-with-ai-coding-assistants) for the `llms.txt` URLs, then ask it to build a request for the endpoint you need. ## Next steps * [Authentication](/docs/guides/authentication) — manage and rotate your API key * [Making requests](/docs/guides/making-requests) — shared request and response structure * [Providers](/docs/guides/providers) — endpoint costs and feature support