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

# Keyword.search

> Run one term on Google, Bing, Reddit, X or LinkedIn and see the ranked results, the AI answer above them and your position.

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

Run one term on Google, Bing, Reddit, X or LinkedIn and see the ranked results, the AI answer above them and your position.

- **Price:** 3¢ per 100 searches
- **Success rate:** 99.1% of 1,710,120 calls
- **Freshness:** Fresh for 1 day
- **Emits:** `Keyword.searched`
- **Capability:** [Market data](https://startupbuilder.dev/systems/marketing/market-data)

```text title="Run it · Ask"
Who shows up on Google for “rent tracking app for landlords”, and where is leaseline.app? Spend up to 1¢.
```

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

const result = await $.try.Keyword.search({ keyword: 'rent tracking app for landlords', venue: 'google-search', maxCost: 0.01 })
```

```json title="Run it · Response"
{
  "keyword": "rent tracking app for landlords",
  "results": [
    {
      "position": 1,
      "url": "https://www.reddit.com/r/Landlord/comments/1kq9",
      "title": "What do you use to track rent for 40 units?",
      "snippet": "Spreadsheet is falling apart, renewal notices keep slipping.",
      "host": "reddit.com",
      "kind": "organic"
    },
    {
      "position": 2,
      "url": "https://rentrival.example/landlords",
      "title": "Rent tracking for small landlords",
      "snippet": "Track rent, late fees and renewals in one place.",
      "host": "rentrival.example",
      "kind": "organic"
    }
  ],
  "blocks": {
    "aiOverview": {
      "text": "Landlords track rent with spreadsheets or with apps that log payments and send reminders.",
      "cited": [
        {
          "url": "https://rentrival.example/landlords",
          "host": "rentrival.example"
        }
      ]
    },
    "peopleAlsoAsk": [
      "How do landlords keep track of rent payments?",
      "Is there a free rent ledger?"
    ],
    "localPack": []
  },
  "tracked": [
    {
      "host": "leaseline.app",
      "position": null,
      "url": null
    }
  ],
  "venue": "google-search",
  "locale": "en-US",
  "observedAt": "2026-10-27T14:02:11Z",
  "method": "sampled",
  "cost": {
    "usd": 0,
    "maxUsd": 0.0003,
    "units": 1,
    "misses": 0
  }
}
```

## Options and prices
| You get | Input that selects it | Price | Success rate |
| --- | --- | --- | --- |
| Google results and your rank | `{"venue":"google-search"}` | About 3¢ per 100 searches | 99.1% of 1,710,120 calls |
| Bing results | `{"venue":"bing"}` | About 3¢ per 100 searches | 90.7% of 5,169 calls |
| Local results around a place | `{"venue":"google-maps","place":"Tacoma, WA"}` | About 10¢ per 100 searches | 99.5% of 223,694 calls |
| News results | `{"venue":"google-news"}` | About 10¢ per 100 searches | 99.95% of 301,427 calls |
| Posts on Reddit | `{"venue":"reddit"}` | About 19¢ per 100 searches | 99.4% of 142,199 calls |
| Posts on X | `{"venue":"x"}` | About 8¢ per 100 searches | 85.6% of 162,439 calls |
| Posts on Hacker News | `{"venue":"hacker-news"}` | About 2¢ per 100 searches | 100% of 1,200 calls |
| Posts on LinkedIn | `{"venue":"linkedin"}` | About 20¢ per 100 searches | 99.97% of 154,260 calls |
| Posts on YouTube | `{"venue":"youtube"}` | About 9¢ per 100 searches | 100% of 9,400 calls |
| Posts on TikTok | `{"venue":"tiktok"}` | About 12¢ per 100 searches | 99.99% of 64,000 calls |
| Posts on Stack Overflow | `{"venue":"stack-overflow"}` | About 1.5¢ per 100 searches | 100% of 9 calls |
| Issues on one GitHub repository that use the term | `{"venue":"github","repo":"rentrival/ledger"}` | About 1.5¢ per 100 searches | 100% of 82 calls |

## Inputs
| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `keyword` | `string` | Required | A phrase or a saved Keyword id. Your brand name as the keyword, with your domain in `exclude`, finds mentions. |
| `venue` | `'google-search' \| 'bing' \| 'google-maps' \| 'google-news' \| 'reddit' \| 'x' \| 'linkedin' \| 'hacker-news' \| 'youtube' \| 'tiktok' \| 'stack-overflow' \| 'github'` | `'google-search'` | Where to search, which also picks the output shape. Each site is its own job with its own price. |
| `repo` | `string` | None. | With `github`: the repository whose issues to read, as `owner/name`. Without it, a GitHub search is refused with `input-missing`. |
| `locale` | `string` | `your home Market` | The market and language, such as `en-US`. |
| `place` | `string` | None. | With `google-maps`: the place to search around, such as `Tacoma, WA`. Without it, a maps search is refused with `input-missing`. |
| `depth` | `number` | `10` | Results to read, at most 100. One price covers any depth. |
| `within` | `string` | `'30d'` | Posts and news only: how far back to look. |
| `track` | [`Hostname[]`](/code/data/types#hostname) | `your Domains` | Report these hosts’ positions in `tracked`. |
| `exclude` | [`Hostname[]`](/code/data/types#hostname) | None. | Drop results on these hosts: pass your own to find mentions you did not write. |
| `maxAge` | `string` | `'1d'` | A stored reading younger than this returns at $0 instead of reading again. |
| `maxCost` | `number` | `0.05` | 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. |
| `results` | `Result[]` | Never null: empty when the site showed nothing, at $0. |
| `results[].position` | `number` | Never null: as the site shows it, ads and blocks counted. |
| `results[].url` | `string` | Never null. |
| `results[].title` | `string` | Never null. |
| `results[].snippet` | `string \| null` | The site shows no snippet. |
| `results[].host` | [`Hostname`](/code/data/types#hostname) | Never null. |
| `results[].kind` | `'organic' \| 'ad' \| 'local' \| 'news' \| 'video' \| 'post' \| 'comment' \| 'question' \| 'issue'` | Never null: which kinds come back depends on `venue` (Output shapes). |
| `results[].author` | `{ handle: string; url: string \| null } \| null` | Absent on web results; `null` on a deleted account. |
| `results[].publishedAt` | `string \| null`, in ISO 8601 | The site shows no date. |
| `results[].engagement` | `{ score?: number; comments?: number; views?: number } \| null` | Absent on web results. |
| `blocks.aiOverview` | `{ text: string; cited: { url: string; host: Hostname }[] } \| null` | The results page shows no AI answer above the results for this term today. |
| `blocks.peopleAlsoAsk` | `string[]` | Never null: empty when the page shows none. |
| `blocks.localPack` | `Result[]` | Never null: empty when the page shows no map pack. |
| `tracked` | `{ host: Hostname; position: number \| null; url: string \| null }[]` | Never null. `position: null` means not in the `depth` read, never position zero. |
| `(page)` | [`Observed`](/code/data/types#observed) | The page carries `observedAt`, `method`, `venue` 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. |

## Output shapes
`venue` picks the shape, and `venue` says which one came back, so your code switches on it.

### A results page
When `venue` is `google-search`, `bing`. Ranked web results, the blocks above them, and where your hosts sit.

| Field | Type | What `null` means |
| --- | --- | --- |
| `results[].kind` | `'organic' \| 'ad' \| 'video' \| 'news'` | Never null. |
| `blocks.aiOverview` | `{ text: string; cited: { url: string; host: Hostname }[] } \| null` | No AI answer above the results for this term today. Bing returns `null` here every time. |
| `blocks.peopleAlsoAsk` | `string[]` | Never null: empty when the page shows none. |
| `tracked[].position` | `number \| null` | Not in the `depth` read, never position zero. |

### Local results around a place
When `venue` is `google-maps`. The map pack for `place`: each business, its rating and its distance order. `place` is required.

| Field | Type | What `null` means |
| --- | --- | --- |
| `results[].kind` | `'local'` | Never null. |
| `blocks.localPack` | `Result[]` | Never null: the same rows as `results`, in map order. |
| `blocks.aiOverview` | `null` | Always null on a maps search. |

### News results
When `venue` is `google-news`. Articles that name the term, newest first within `within`.

| Field | Type | What `null` means |
| --- | --- | --- |
| `results[].kind` | `'news'` | Never null. |
| `results[].publishedAt` | `string \| null` | The outlet shows no date. |

### Posts
When `venue` is `reddit`, `x`, `linkedin`, `hacker-news`, `youtube`, `tiktok`, `stack-overflow`, `github`. Posts and comments that use the term, with who wrote them and how far they spread. There is no results page, so `blocks` is empty and `tracked` lists any post on your own hosts.

| Field | Type | What `null` means |
| --- | --- | --- |
| `results[].kind` | `'post' \| 'comment' \| 'video' \| 'question' \| 'issue'` | Never null: `video` on YouTube and TikTok, `question` on Stack Overflow, `issue` on GitHub. |
| `results[].author` | `{ handle: string; url: string \| null } \| null` | `null` when the account was deleted. |
| `results[].publishedAt` | `string \| null` | The site shows no date. |
| `results[].engagement` | `{ score?: number; comments?: number; views?: number }` | Each count is absent where the site does not show it: X shows no score, Hacker News no views. |

```json
{
  "venue": "google-search",
  "results": [
    {
      "position": 2,
      "url": "https://rentrival.example/landlords",
      "title": "Rent tracking for small landlords",
      "host": "rentrival.example",
      "kind": "organic"
    }
  ],
  "blocks": {
    "aiOverview": null,
    "peopleAlsoAsk": [
      "Is there a free rent ledger?"
    ],
    "localPack": []
  },
  "tracked": [
    {
      "host": "leaseline.app",
      "position": 18,
      "url": "https://leaseline.app/rent-ledger-template"
    }
  ]
}
```

```json
{
  "venue": "google-maps",
  "results": [
    {
      "position": 1,
      "url": "https://maps.google.com/?cid=4419",
      "title": "Harborview Property Management",
      "host": "harborview-pm.com",
      "kind": "local",
      "snippet": "4.6 stars, 212 reviews"
    }
  ],
  "blocks": {
    "aiOverview": null,
    "peopleAlsoAsk": [],
    "localPack": []
  },
  "tracked": []
}
```

```json
{
  "venue": "google-news",
  "results": [
    {
      "position": 1,
      "url": "https://news.example/rent-reporting-2026",
      "title": "Small landlords turn to apps as rent reporting rules change",
      "host": "news.example",
      "kind": "news",
      "publishedAt": "2026-10-22T09:00:00Z"
    }
  ],
  "blocks": {
    "aiOverview": null,
    "peopleAlsoAsk": [],
    "localPack": []
  },
  "tracked": []
}
```

```json
{
  "venue": "reddit",
  "results": [
    {
      "position": 1,
      "url": "https://www.reddit.com/r/Landlord/comments/1kq9",
      "title": "What do you use to track rent for 40 units?",
      "host": "reddit.com",
      "kind": "post",
      "author": {
        "handle": "u/fourplexfred",
        "url": "https://www.reddit.com/user/fourplexfred"
      },
      "publishedAt": "2026-10-25T16:40:00Z",
      "engagement": {
        "score": 48,
        "comments": 31
      }
    }
  ],
  "blocks": {
    "aiOverview": null,
    "peopleAlsoAsk": [],
    "localPack": []
  },
  "tracked": []
}
```

## Read the result
| Field | Value | What it means | What to do |
| --- | --- | --- | --- |
| `tracked.position` | 8 to 30 at week 6 | The page is indexed and in range, but trusted less than the pages above it. | Rewrite it from the query’s wording, and link it from your 3 strongest pages. (the week-6 rule in the search playbook) |
| `tracked.position` | `null` with impressions after week 6 | The page is shown for the term but never reaches the top 100: it targets the wrong term. | Re-pick a term at or under your difficulty ceiling. (the week-6 rule in the search playbook) |
| `blocks.aiOverview` | Present | Position-one click rates on such terms fell 58% to 61% in two studies. | Answer the question in your first 50 words; the cited pages are the ones to beat. (two click-rate studies of terms with an AI answer above the results) |
| `results[].host` | 3 or more of the top 10 hold your referring domains or fewer | The term is winnable with the links you can reach in 8 weeks. | Read each one’s referring domains with `Domain.benchmark`, then approve the term. (the winnability rule in the search playbook) |
| `results[].kind` | `post` with 10 or more comments this month | Buyers are asking about the problem right now, in public. | Answer the thread with a sourced reply, not a pitch. (our planning number) |

## Effects
**Action class**

None: it reads, so no approval rule names it

**Modes**

- `try`: Runs the read and returns real rows; nothing is saved and nothing is billed.
- `send`: Returns a pending Action at once; its Event carries the rows when the read finishes.
- `do`: Waits for the read and returns the rows, billed per result.

**Idempotency**

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

**Saved or shared**

Nothing is written to your records. A results page is shared across startups for the same term, site and locale for a day; `tracked` is yours.

**Emits**

`Keyword.searched` with `{ keyword, venue, results, blocks, tracked }`

**Performed by**

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

**Price**

About 3¢ per 100 searches. Free: a page read younger than `maxAge`. `maxCost` defaults to 5¢. 30 results pages for a first keyword map: 30 × 0.03¢ a search is about 0.9¢.

**Reliability**

The first route succeeds 99.1% of 1,710,120 calls. If it fails, a second route answers at 99.9% of 148,816 calls, and you pay for one.

**Freshness**

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

**Batch and pages**

Up to 1 term a call, up to 100 results deep. It answers in one response.

**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
Returns in one response; nothing waits.

## Example

**Ask**

```text
Who shows up on Google for “rent tracking app for landlords”, and where
is leaseline.app? Spend up to 1¢.
```

**API**

```bash
curl -X POST "https://api.sb/leaseline.app/keywords/search?mode=try" \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keyword":"rent tracking app for landlords","venue":"google-search","maxCost":0.01}'
```

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

**CLI**

```bash
npx startup-builder do Keyword.search --try --data '{"keyword":"rent tracking app for landlords","venue":"google-search","maxCost":0.01}'
```

**MCP**

```json
{
  "name": "do",
  "arguments": {
    "mode": "try",
    "code": "return await $.Keyword.search({ keyword: 'rent tracking app for landlords', venue: 'google-search', maxCost: 0.01 })"
  }
}
```

**SDK**

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

const result = await $.try.Keyword.search({
  keyword: 'rent tracking app for landlords',
  venue: 'google-search',
  maxCost: 0.01,
})
```

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.search',
  input: { keyword: 'rent tracking app for landlords' },
  change: 'rank-change',
  threshold: 3,
  every: '1d',
})
```

## Used by
- **Workflows:** [Keyword opportunity map](/capabilities/research-demand/keyword-opportunity-map) · [Search readout](/capabilities/measure/search-readout) · [Pick who to sell to first](/capabilities/find-customers/pick-who-to-sell-to) · [Post-engager harvest](/capabilities/find-customers/post-engagers)
- **Agent tasks:** [Answer page per question](/capabilities/make-content/answer-pages) · [Answer threads](/capabilities/publish-and-post/answer-threads) · [Launch day](/capabilities/earn-first-links/launch-day) · [Monthly search content](/capabilities/make-content/monthly-search-content) · [Account brief](/capabilities/reach-out/account-brief) · [Message test](/capabilities/run-ads/message-test)
- **Services:** [Ranked page](/capabilities/make-content/ranked-page)
- **Jobs:** [Check AI answers](/capabilities/check-ai-answers) · [Earn first links](/capabilities/earn-first-links) · [Get indexed](/capabilities/get-indexed) · [Make content](/capabilities/make-content) · [Measure](/capabilities/measure) · [Publish and post](/capabilities/publish-and-post) · [Research demand](/capabilities/research-demand)

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