AI Answer Sampling
Asks the same prompt several times and returns how the answers differed — per business, how many runs named it, its best, worst and average position, and the text of every run.
When to use this
A client asking "am I showing up in ChatGPT?" is asking for a rate, and one call cannot give one. A model composes its answer per request, so ask twice ten minutes apart and the businesses and their order can differ. This runs the prompt 2-25 times and hands back the distribution: named in 8 of 10 runs, average position 2.1.
POST
/v1/ai/sampleCost: samples × the per-run price: 4 credits a run on chat_gpt and gemini, 2 on google_ai_mode. Ten samples on chat_gpt is 40 credits.
Rate Limit: Varies by plan
Response: Asynchronous. The samples run in parallel upstream, so the wait is the slowest run, not samples × one run. Measured over ten samples per platform: median ~27 seconds, nine of ten under 80, slowest 101s on the scrapers and 129s on AI Mode.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| keyword | string | Yes | The prompt or keyword to ask repeatedly (max 500 chars) |
| platform | string | Yes | "chat_gpt", "gemini" or "google_ai_mode". The mention endpoints are not sampleable — they read stored aggregates, so running one ten times returns the same numbers ten times. |
| samples | number | Yes | How many times to run the prompt, 2-25. Below about 5 the rate is too coarse to act on; 10 is the usual figure for daily tracking. |
| location | string | No | Location (e.g. "Orchard Park, NY"). Required on google_ai_mode, which is a Google search and depends on where it runs from. Gemini supports city-level; ChatGPT resolves to country. |
| force_web_search | boolean | No | Require the platform to search the web before answering. chat_gpt only; gemini returns a 400. |
| language | string | No | Language to ask in, as an ISO 639-1 code or name ("fr", "French"). Defaults to the language of the market you pass. |
| callback_url | string | No | Public https URL to POST the finished sample to, instead of polling. |
Example Request
curl -X POST https://api.localseodata.com/v1/ai/sample \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"keyword": "best plumber in Orchard Park NY",
"platform": "chat_gpt",
"samples": 10
}'Example Response
JSON
{
"status": "success",
"credits_used": 40,
"data": {
"job_id": "sample_V1StGXR8Z5",
"status": "complete",
"keyword": "best plumber in Orchard Park NY",
"platform": "chat_gpt",
"samples_requested": 10,
"samples_returned": 10,
"samples_failed": 0,
"businesses": [
{
"name": "Bolt Plumbing",
"matched_on": "domain",
"cid": "",
"domain": "boltplumbing.com",
"category": "Plumber",
"rating": 4.8,
"reviews_count": 412,
"address": "1200 Southwestern Blvd, Orchard Park, NY",
"phone": "+1 716-555-0142",
"website": "https://boltplumbing.com",
"appearances": 8,
"appearance_rate": 0.8,
"best_rank": 1,
"worst_rank": 4,
"mean_rank": 2.13,
"runs_seen": [1, 2, 3, 5, 6, 8, 9, 10]
}
],
"runs": [
{
"run": 1,
"ok": true,
"businesses": [],
"response_text": "For plumbing in Orchard Park, the names that come up most are..."
}
]
}
}Response Fields
| Field | Type | Description |
|---|---|---|
| samples_requested | integer | How many runs were asked for |
| samples_returned | integer | How many came back. Every rate below is out of this number |
| samples_failed | integer | Runs the provider did not deliver. These are refunded |
| businesses[].name | string | Business name, as the last run that named it wrote it |
| businesses[].matched_on | string | "cid", "domain" or "name" — how runs were tied together into one business |
| businesses[].cid | string | Google's identifier in decimal, on google_ai_mode only. Empty elsewhere |
| businesses[].appearances | integer | How many runs named this business |
| businesses[].appearance_rate | float | appearances / samples_returned, to 3 places |
| businesses[].best_rank | integer | Highest position it reached in any run |
| businesses[].worst_rank | integer | Lowest position it reached in any run |
| businesses[].mean_rank | float | Average position across the runs that named it. Absent runs are not counted |
| businesses[].runs_seen | array | Which run numbers named it |
| runs[].run | integer | 1-based run number |
| runs[].ok | boolean | Whether that run came back |
| runs[].businesses | array | Businesses named in that run, in the same shape as /v1/serp/local-pack |
| runs[].response_text | string | The answer text for that run |
| runs[].error | string | The provider's message, on failed runs only |
Notes
- appearance_rate is out of samples_returned, not samples_requested. A run the provider failed to deliver is a measurement that was not taken, so it is refunded rather than counted as an absence — samples_failed says how many there were.
- mean_rank averages only the runs that named the business. Named 2nd in 3 of 10 runs reads mean_rank 2 and appearance_rate 0.3; the two say different things and which one is bad is the actionable part.
- matched_on says how runs were tied together: "cid" is Google's own identifier and is exact, "domain" is domain plus name (never domain alone — two branches of a chain share one), "name" is the string alone and is the weakest.
- On google_ai_mode prefer the cid. Google labels cards editorially, so the same business can be named differently between runs and a name-keyed count would report it twice at half the rate each.
- Results are never cached. Serving a sample from cache would answer a later question with an earlier answer, which is the failure this endpoint exists to avoid.