Keyword .measure
Measure the terms you name in one locale: monthly searches, cost per click, difficulty from 0 to 100, intent and the 12-month trend.
- Price
- 18¢ per 100 terms
- Success rate
- 99.94% of 7,106 calls
- Freshness
- Fresh for 30 days
- Emits
Keyword.measured- Capability
- Market data
POST https: //api.sb/ {your-domain}/ keywords/ measure
Options and prices
Inputs
| Name | Type | Default | Meaning |
|---|---|---|---|
keywords | string[] | None. | Up to 1,000 phrases, measured in one call. Pass this or keyword. |
keyword | Keyword | None. | A saved Keyword id. Also served at /, as the hub defines it. |
venue | 'google-search' | 'bing' | 'google-search' | The search site. An answer engine or a forum is refused with venue-unsupported: those are other calls. |
locale | string | your home Market | The market and language, such as en-US. |
measures | ('searches' | 'cpc' | 'difficulty' | 'intent' | 'trend' | 'aiSearches')[] | every one but 'aiSearches' | What to measure. Each measure is its own job with its own price. |
maxAge | string | '30d' | A stored reading younger than this returns at $0 instead of reading again. |
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 |
|---|---|---|---|
measurements | Keyword[] | None. | Never null: one row per term sent, in order. |
measurements[].term | string | None. | Never null. |
measurements[].searches | number | null | searches a month, 12-month average | Unknown: no reading. Never zero demand, and never billed. |
measurements[].cpc | number | null | USD a click | No advertiser bids on it, or no reading. |
measurements[].competition | number | null | 0 to 1, advertisers bidding | No reading. |
measurements[].difficulty | number | null | 0 to 100 | Unknown, or not in measures. |
measurements[].intent | 'buy' | 'compare' | 'learn' | 'navigate' | null | None. | Unknown, or not in measures. |
measurements[].trend | 'rising' | 'flat' | 'falling' | null | 12 months | Under 12 months of readings. |
measurements[].history | { month: | searches a month | Absent without trend; a month with no reading is left out. |
measurements[].aiSearches | number | null | asks a month | Absent without aiSearches; null is no reading. |
measurements[] | Observed | None. | Each row also carries observedAt, method, venue and locale. |
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 |
|---|---|---|---|
difficulty | 10, 20, 30, 40, 50, 60, 70 | About 10, 22, 36, 56, 84, 129 and 202 referring domains to reach page one: the scale the score is defined on. | Your ceiling is the difficulty your referring domains reach. A new domain with 8 sits near 10; target at or under it. (the published referring-domain scale) |
difficulty | Low on a local term | The score understates the map pack: one review read 7 for a term owned by local listings. | Read the results page with Keyword.search before you trust it. (reviews of the difficulty read) |
searches | null | Unknown, not zero. You were not billed for it. | Keep the term if a buyer writes it in a problem post. (the contract every data call keeps) |
cpc | Over $4.29 for a $149 a month product | Above break-even: 80% margin × 12 months × a 0.3% visit-to-paid rate. | Earn this click with a page; do not buy it. (break-even cost per click, the strategy numbers) |
trend | falling | Demand is leaving the term. | Drop it if history fell more than 30% across 12 months. (our planning number) |
Effects
- Action class
None: no approval rule names it, so it runs within its seat’s Permissions and Limits
- 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
Each measurement is an immutable
Keyword.measuredEvent; nothing is edited. Measurements are shared across every startup that measures the same phrase and locale.- Emits
Keyword.measuredwith{ term,searches, cpc, competition, difficulty, intent, trend, venue, locale }, one Event per term - Performed by
- Price
About 18¢ per 100 terms. Free: a reading younger than
maxAge, a term with no reading, volume and cost per click from your own Google Ads account.maxCostdefaults to 50¢. 500 terms, every measure: 500 × 0.18¢ a term is about 90¢.- Reliability
The first route succeeds 99.94% of 7,106 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
How many people search “rent ledger template” a month, what does a click
cost, and how hard is page one? Spend up to 1¢. 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 |
|---|---|---|
rent ledger template | {"term": | 1,900 searches a month, $2.40 a click and difficulty 18, every time. |
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.measure',
input: { keywords: ['rent ledger template'], measures: ['searches', 'cpc'] },
change: 'volume-change',
every: '4w',
})Used by
- Workflows: Keyword opportunity map · Pick who to sell to first
- Agent tasks: Monthly search content
- Jobs: Research demand · Run ads