URL: /code/data/keywords/ask · Id: `page:/code/data/keywords/ask`

# Keyword.ask

> Ask ChatGPT, Gemini, Perplexity and the other answer engines a buyer’s question and see whether they name you and which pages they cite.

`POST https://api.sb/{your-domain}/keywords/ask`

Ask ChatGPT, Gemini, Perplexity and the other answer engines a buyer’s question and see whether they name you and which pages they cite.

- **Price:** 0.4¢ an answer
- **Success rate:** 99.96% of 131,093 calls
- **Freshness:** Fresh for 7 days
- **Emits:** `Keyword.asked`
- **Capability:** [Market data](https://startupbuilder.dev/systems/marketing/market-data)

```text title="Run it · Ask"
Do ChatGPT and Perplexity name Leaseline when a 40-unit property manager asks for rent tracking software? Spend up to 2¢.
```

```ts title="Run it · SDK"
import { $ } from 'startup-builder'

const result = await $.try.Keyword.ask({ keyword: 'best rent tracking software for a 40-unit property manager', venues: ['chatgpt', 'perplexity'], maxCost: 0.02 })
```

```json title="Run it · Response"
{
  "keyword": "best rent tracking software for a 40-unit property manager",
  "answers": [
    {
      "venue": "chatgpt",
      "run": 1,
      "text": "For 20 to 100 units, most managers compare three kinds of tool: a spreadsheet template, a full property management suite, or a focused rent and renewal tracker such as RentRival or Leaseline.",
      "named": [
        {
          "brand": "RentRival",
          "position": 1,
          "host": "rentrival.example"
        },
        {
          "brand": "Leaseline",
          "position": 2,
          "host": "leaseline.app"
        }
      ],
      "cited": [
        {
          "position": 1,
          "url": "https://rentrival.example/compare",
          "host": "rentrival.example",
          "title": "Rent software compared"
        },
        {
          "position": 2,
          "url": "https://www.reddit.com/r/PropertyManagement/comments/1m2x",
          "host": "reddit.com",
          "title": null
        }
      ],
      "queries": [
        "rent tracking software small property managers 2026"
      ],
      "mentioned": [
        {
          "brand": "Leaseline",
          "named": true,
          "cited": false
        }
      ],
      "locale": "en-US",
      "observedAt": "2026-10-27T14:02:11Z",
      "method": "sampled"
    },
    {
      "venue": "perplexity",
      "run": 1,
      "text": "Small property managers most often recommend RentRival for rent tracking, according to threads in r/PropertyManagement.",
      "named": [
        {
          "brand": "RentRival",
          "position": 1,
          "host": "rentrival.example"
        }
      ],
      "cited": [
        {
          "position": 1,
          "url": "https://www.reddit.com/r/PropertyManagement/comments/1m2x",
          "host": "reddit.com",
          "title": null
        }
      ],
      "queries": [],
      "mentioned": [
        {
          "brand": "Leaseline",
          "named": false,
          "cited": false
        }
      ],
      "locale": "en-US",
      "observedAt": "2026-10-27T14:02:11Z",
      "method": "sampled"
    }
  ],
  "cost": {
    "usd": 0,
    "maxUsd": 0.01,
    "units": 2,
    "misses": 0
  }
}
```

## Options and prices
| You get | Input that selects it | Price | Success rate |
| --- | --- | --- | --- |
| An answer from ChatGPT | `{"venues":["chatgpt"]}` | About 0.4¢ an answer | 99.96% of 131,093 calls |
| An answer from Gemini | `{"venues":["gemini"]}` | About 0.24¢ an answer | 98.6% of 69,436 calls |
| An answer from Perplexity | `{"venues":["perplexity"]}` | About 0.6¢ an answer | 99.98% of 69,361 calls |
| An answer from Copilot | `{"venues":["copilot"]}` | About 0.28¢ an answer | 99.6% of 34,753 calls |
| An answer from Claude | `{"venues":["claude"]}` | About 2.5¢ an answer | 100% of 17,151 calls |
| An answer from Google AI Mode | `{"venues":["google-ai-mode"]}` | About 0.24¢ an answer | 99.8% of 62,793 calls |

## Inputs
| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `keyword` | `string` | Required | The question, worded as a buyer would type it to an assistant. |
| `venues` | `('chatgpt' \| 'gemini' \| 'perplexity' \| 'copilot' \| 'claude' \| 'google-ai-mode')[]` | `['chatgpt', 'gemini', 'perplexity', 'google-ai-mode']` | The engines to ask, in parallel. Each is its own job with its own price. |
| `runs` | `number` | `1` | Times to ask each engine, 1 to 3. Answers vary run to run, so a tracked question takes 3. |
| `country` | `string` | `your home Market` | Where the question is asked from, signed out. |
| `brands` | `string[]` | `your Brand and your rivals` | The brands `mentioned` reports on, named or cited. |
| `maxAge` | `string` | `'7d'` | A stored reading younger than this returns at $0 instead of reading again. |
| `maxCost` | `number` | `0.10` | The most this call may spend, in USD. Over it, the call is refused before it spends, or stops early with what it has. |

## Outputs
| Field | Type | What `null` means |
| --- | --- | --- |
| `keyword` | `string` | Never null. |
| `answers` | `Answer[]` | Never null: one per engine per run. |
| `answers[].venue` | [`VenueId`](/code/data/types#venueid) | Never null. |
| `answers[].run` | `number`, in 1 to 3 | Never null. |
| `answers[].text` | `string` | Never null: the answer as shown. |
| `answers[].named` | `{ brand: string; position: number; host: Hostname \| null }[]` | Never null: brands in order of first mention; empty when none is named. |
| `answers[].cited` | `{ position: number; url: string; host: Hostname; title: string \| null }[]` | Never null: empty when the engine cites nothing. |
| `answers[].queries` | `string[]` | Never null: the searches the engine ran, empty when it does not show them. |
| `answers[].mentioned` | `{ brand: string; named: boolean; cited: boolean }[]` | Never null: one row per brand in `brands`. |
| `answers[]` | [`Observed`](/code/data/types#observed) | Each answer also carries `observedAt`, `method` and `locale`. |
| `cost` | [`Cost`](/code/data/types#cost), in USD | Never null. In try mode `usd` is 0 and `maxUsd` is what the call would have cost. |

## Read the result
| Field | Value | What it means | What to do |
| --- | --- | --- | --- |
| `runs` | 1 | One answer is an anecdote: the same engine names different brands on the next run. | Ask a tracked question 3 times on each engine. (the answer-share sampling rule) |
| `mentioned.named` | Share across 200 answers | At a 10% share, 200 answers read ±4.2 points. | Flag only a move of 5 points or more that holds for 2 weeks; anything less is noise. (binomial interval, the strategy numbers) |
| `cited` | Reddit threads on Perplexity, encyclopedic pages on ChatGPT | Engines cite different sources: Reddit is 46.7% of Perplexity’s top-10 cited domains, and only about 11% of domains are cited by both. | Read each engine on its own and plan per engine; never blend them into one score. (a study of about 680M citations) |
| `mentioned.cited` | `false` everywhere | Branded mentions on the web track AI visibility more closely (0.664) than referring domains do (0.295). | Get listed and mentioned on the pages engines already cite before you build links. (a correlation study of 75,000 brands) |
| `queries` | Searches the engine ran | The engine looked these up before it answered. | Measure them with `Keyword.measure`; a page that ranks for them gets cited. (the AEO playbook) |

## Effects
**Action class**

None: it reads, so no approval rule names it

**Modes**

- `try`: Asks the engines and returns real answers; nothing is saved and nothing is billed.
- `send`: Returns a pending Action at once; `Keyword.asked` carries the answers when every engine has replied.
- `do`: Waits about 30 seconds for every engine, then returns the answers, billed per answer.

**Idempotency**

The same `Idempotency-Key` with the same input replays the first answers at $0; with other input it returns 409.

**Saved or shared**

Nothing is written to your records. Answers are shared across startups for the same question, engine and country for 7 days; `mentioned` is yours.

**Emits**

`Keyword.asked` with `{ keyword, venue, run, named, cited, mentioned }, one Event per answer`

**Performed by**

[Analyst](https://startupbuilder.dev/agents/marketing/analyst)

**Price**

About 0.4¢ an answer. Free: an answer younger than `maxAge`. `maxCost` defaults to 10¢. 20 buying questions on 4 engines: 80 × 0.4¢ an answer is about 32¢.

**Reliability**

The first route succeeds 99.96% of 131,093 calls. If it fails, a second route answers at 97.9% of 109,468 calls, and you pay for one.

**Freshness**

A reading younger than 7 days returns at $0. Readings are shared across startups for the same subject and locale.

**Batch and pages**

Up to 18 answers a call: 6 engines × 3 runs. An answer takes about 30 seconds, so `send` returns a pending Action at once and `Keyword.asked` delivers the answers

**Rate**

[The shared rate rules](/code/data#rate): 10 calls a second per key, and an array input counts as one call.

## Refuses when
| Check | Error | Fix | Then call |
| --- | --- | --- | --- |
| A search engine, answer engine or network the call does not read, such as ChatGPT sent to `Keyword.measure` | [`venue-unsupported`](/code/refusals#venue-unsupported) | Pick one the call’s `venue` input lists; AI answers are `Keyword.ask`, posts are `Keyword.search`. | [`Keyword.ask`](/code/data/keywords/ask) |

Every data call can also meet [the shared data-call refusals](/code/refusals#data-calls): [`filter-unsupported`](/code/refusals#filter-unsupported), [`input-missing`](/code/refusals#input-missing), [`limit-exceeded`](/code/refusals#limit-exceeded), [`max-cost`](/code/refusals#max-cost), [`rate-limited`](/code/refusals#rate-limited), [`sources-busy`](/code/refusals#sources-busy).

```json
{
  "type": "https://startupbuilder.dev/errors/venue-unsupported",
  "title": "The call does not read that search engine or network",
  "status": 422,
  "detail": "A search engine, answer engine or network the call does not read, such as ChatGPT sent to `Keyword.measure`.",
  "fix": "Pick one the call’s `venue` input lists; AI answers are `Keyword.ask`, posts are `Keyword.search`.",
  "next": {
    "verb": "Keyword.ask",
    "input": {
      "keyword": "best rent tracking software for a 40-unit property manager",
      "venues": [
        "chatgpt"
      ]
    }
  },
  "instance": "https://api.sb/leaseline.app/actions/action_1"
}
```

## When it waits
| Fact | Value |
| --- | --- |
| `send` returns | A pending Action, `status: running`, with its `$id`. |
| Delivered by | `Keyword.asked` |

```ts
await $.Watch.create({
  selector: 'Keyword.asked',
  url: 'https://leaseline.app/hooks/answers',
})
```

## Example

**Ask**

```text
Do ChatGPT and Perplexity name Leaseline when a 40-unit property manager
asks for rent tracking software? Spend up to 2¢.
```

**API**

```bash
curl -X POST "https://api.sb/leaseline.app/keywords/ask?mode=try" \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keyword":"best rent tracking software for a 40-unit property manager","venues":["chatgpt","perplexity"],"maxCost":0.02}'
```

The response is the one in the Run it window above.

**CLI**

```bash
npx startup-builder do Keyword.ask --try --data '{"keyword":"best rent tracking software for a 40-unit property manager","venues":["chatgpt","perplexity"],"maxCost":0.02}'
```

**MCP**

```json
{
  "name": "do",
  "arguments": {
    "mode": "try",
    "code": "return await $.Keyword.ask({ keyword: 'best rent tracking software for a 40-unit property manager', venues: ['chatgpt', 'perplexity'], maxCost: 0.02 })"
  }
}
```

**SDK**

```ts
import { $ } from 'startup-builder'

const result = await $.try.Keyword.ask({
  keyword: 'best rent tracking software for a 40-unit property manager',
  venues: ['chatgpt', 'perplexity'],
  maxCost: 0.02,
})
```

To retry, send the same `Idempotency-Key`: the retry returns the first answer for $0. A new key reads again.

A `sk_test_` key answers these the same way every time, at $0 ([every test value](/code/data#test-values)).

| Test value | Always returns | What it is for |
| --- | --- | --- |
| `maxCost: 0.0001` | Refuses with [`max-cost`](/code/refusals#max-cost) | Any priced call with a maxCost of 0.0001 is refused before it spends: the price is above the cap. |
| `rate-limited.test` | Refuses with [`rate-limited`](/code/refusals#rate-limited) | Any call naming this hostname answers 429 with Retry-After, so your retry logic runs. |
| `sources-busy.test` | Refuses with [`sources-busy`](/code/refusals#sources-busy) | Any call naming this hostname answers 503 as if every route were busy, and nothing is spent. |

Watch it on a schedule: a Monitor runs this call and records an Event only when the reading changes.

```ts
await $.Monitor.create({
  verb: 'Keyword.ask',
  input: { keyword: 'best rent tracking software for a 40-unit property manager', runs: 3 },
  change: 'answer-citation',
  every: '1w',
})
```

## Used by
- **Workflows:** [AI answer audit](/capabilities/check-ai-answers/ai-answer-audit) · [Search readout](/capabilities/measure/search-readout)
- **Agent tasks:** [Answer page per question](/capabilities/make-content/answer-pages) · [Monthly search content](/capabilities/make-content/monthly-search-content)
- **Jobs:** [Check AI answers](/capabilities/check-ai-answers) · [Measure](/capabilities/measure)

## Related
[`Keyword.find`](/code/data/keywords/find) · [`Keyword.measure`](/code/data/keywords/measure) · [`Keyword.forecast`](/code/data/keywords/forecast) · [`Keyword.search`](/code/data/keywords/search)