Keyword .find
Find the terms worth a page: ideas from your seed words, buyers’ questions, and the terms a rival ranks or pays for.
- Price
- 5¢ per 100 ideas
- Success rate
- 100% of 16,219 calls
- Freshness
- Fresh for 30 days
- Emits
Keyword.found- Capability
- Market data
POST https: //api.sb/ {your-domain}/ keywords/ find
Options and prices
Inputs
| Name | Type | Default | Meaning |
|---|---|---|---|
seeds | string[] | None. | Up to 20 phrases. Seed from the words buyers used in problem posts and calls, not from your product’s own words. Pass seeds or rival. |
rival | Hostname | None. | The terms this domain ranks for, or pays for with mode:. |
exclude | Hostname | None. | With rival: drop the terms this domain already ranks for. Pass your own to get the content gap. |
mode | 'ideas' | 'suggestions' | 'questions' | 'rival' | 'rival-paid' | 'trending' | 'ideas' with seeds, | Which job runs. Each one returns the same row. |
venue | VenueId | 'google-search' | youtube and tiktok take suggestions; tiktok, x and reddit take trending. |
locale | string | your home Market | The market and language, such as en-US. |
where | { searches? | None. | Filters run before the list returns, so you never pay for a term you would drop. |
limit | number | 100 | Terms to return, at most 1,000. You pay for terms returned. |
save | number | 0 | Save the first n terms as Keywords; free, and it follows the call’s Mode. |
maxCost | number | 0.50 | 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 | Unit | What null means |
|---|---|---|---|
terms | Keyword[] | None. | Never null: empty when nothing passed where, at $0. |
terms[].term | string | None. | Never null. |
terms[].searches | number | null | searches a month | Unknown: no reading for a long-tail term. Never zero demand. |
terms[].cpc | number | null | USD a click | No advertiser bids on it, or no reading. |
terms[].difficulty | number | null | 0 to 100 | Unknown. |
terms[].intent | 'buy' | 'compare' | 'learn' | 'navigate' | null | None. | Unknown. |
terms[].trend | 'rising' | 'flat' | 'falling' | null | 12 months | Under 12 months of readings. |
terms[].position | number | null | None. | Absent without rival; null past position 100. |
terms[].rivalUrl | string | null | None. | Absent without rival. |
terms[].monthlyAdCost | number | null | USD a month | Absent unless mode:. |
terms[].posts | number | null | posts this week | Absent unless mode:. |
terms[] | Observed | None. | Each row also carries observedAt, method, venue and locale. |
found | number | terms | Never null: before where ran. |
kept | number | terms | Never null: after where ran; you pay for these. |
peopleAlsoAsk | string[] | None. | Never null: empty when the results page shows none. |
ignored | string[] | None. | Never null: empty when every filter you sent was applied. |
cost | Cost | 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 |
|---|---|---|---|
searches | null | Unknown, not zero: most long-tail B2B terms have no reading. | Keep it if a problem post uses the phrase; drop it only when it reads null and no buyer writes it. (the drop rule in the search playbook) |
difficulty | Above your ceiling | Your ceiling is the difficulty your referring domains reach; above it, page one needs links you do not have yet. | Park it for week 12; target the terms at or under the ceiling now. (the referring-domain scale difficulty is defined on) |
trend | falling | Demand is leaving the term. | Drop it if it fell more than 30% over 12 months. (our planning number) |
intent | navigate to a rival’s brand | The searcher wants that site, not a choice. | Drop it; a page cannot win a search for someone else’s login page. (the intent rule in the search playbook) |
kept | Under 60 of 300 | The seeds are your words, not the buyer’s. | Reseed from the phrases in problem posts and People also ask. (our planning number: 200 to 500 ideas leave about 60) |
monthlyAdCost | Over $0 on a term you want | A rival pays for it, so the term converts for someone. | Earn it if its cost per click is over your break-even; buy it only if under. (break-even cost per click, the strategy numbers) |
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-Keywith the same input replays the first answer at $0; with other input it returns 409.- Saved or shared
Terms are returned, not saved, unless you pass
save. Ideas are shared across startups for the same seeds and locale for 30 days.- Emits
Keyword.foundwith{ terms,found, kept, seeds, rival } - Performed by
- Price
About 5¢ per 100 ideas. Free: ideas from your own Google Ads account, saving terms as Keywords, ideas filtered out by
wherebefore they return.maxCostdefaults to 50¢. 300 ideas from 5 seeds: 300 × 0.05¢ an idea is about 15¢.- Reliability
The first route succeeds 100% of 16,219 calls. If it fails, the call refuses with
sources-busyand nothing is spent.- Freshness
A reading younger than 30 days returns at $0. Readings are shared across startups for the same subject and locale.
- Batch and pages
Up to 1,000 terms a call. It answers in one response.
- Rate
The shared rate rules: 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 | Pick one the call’s venue input lists; AI answers are Keyword.ask, posts are Keyword.search. | Keyword.ask |
Every data call can also meet the shared data-call refusals: filter-unsupported, input-missing, limit-exceeded, max-cost, rate-limited, sources-busy.
The venue-unsupported payload
{
"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
Which rent-tracking search terms are worth a page, at difficulty 30 or
under? Spend up to 20¢. To retry, send the same Idempotency-Key: the retry returns the first answer for $0. A new key reads again.
Test values, and watching it
A sk_test_ key answers these the same way every time, at $0 (every test value).
| Test value | Always returns | What it is for |
|---|---|---|
maxCost: | Refuses with 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 | Any call naming this hostname answers 429 with Retry-After, so your retry logic runs. |
sources-busy.test | Refuses with 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.
await $.Monitor.create({
verb: 'Keyword.find',
input: { mode: 'trending', venue: 'tiktok' },
change: 'new-term',
every: '1w',
})Used by
- Workflows: Competitor gap · Keyword opportunity map
- Agent tasks: Monthly search content
- Jobs: Research demand · Size up competitors