Skip to content

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

You getInput that selects itPriceSuccess rate
All five measures in one rowThe defaultAbout 18¢ per 100 terms99.94% of 7,106 calls
Monthly searches and cost per click{"measures":["searches","cpc"]}About 5¢ per 100 terms100% of 14,260 calls
Difficulty, for up to 1,000 terms at once{"measures":["difficulty"]}About 1.2¢ a batch of up to 1,000 terms100% of 1,360 calls
Intent, for up to 1,000 terms at once{"measures":["intent"]}About 1.2¢ a batch of up to 1,000 terms99.8% of 588 calls
The 12-month trend{"measures":["trend"]}About 12¢ per 100 terms97.9% of 7,973 calls
How often the term is asked of AI tools{"measures":["aiSearches"]}About 1¢ a term100% of 1,137 calls
The same measures on Bing{"venue":"bing"}About 9¢ a batch of up to 1,000 terms100% of 80 calls

Inputs

NameTypeDefaultMeaning
keywordsstring[]None.Up to 1,000 phrases, measured in one call. Pass this or keyword.
keywordKeywordNone.A saved Keyword id. Also served at /keywords/{keyword}/measure, 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.
localestringyour home MarketThe 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.
maxAgestring'30d'A stored reading younger than this returns at $0 instead of reading again.
maxCostnumber0.50The most this call may spend, in USD. Over it, the call is refused before it spends, or stops early with what it has.

Outputs

FieldTypeUnitWhat null means
measurementsKeyword[]None.Never null: one row per term sent, in order.
measurements[].termstringNone.Never null.
measurements[].searchesnumber | nullsearches a month, 12-month averageUnknown: no reading. Never zero demand, and never billed.
measurements[].cpcnumber | nullUSD a clickNo advertiser bids on it, or no reading.
measurements[].competitionnumber | null0 to 1, advertisers biddingNo reading.
measurements[].difficultynumber | null0 to 100Unknown, or not in measures.
measurements[].intent'buy' | 'compare' | 'learn' | 'navigate' | nullNone.Unknown, or not in measures.
measurements[].trend'rising' | 'flat' | 'falling' | null12 monthsUnder 12 months of readings.
measurements[].history{ month: string; searches: number }[]searches a monthAbsent without trend; a month with no reading is left out.
measurements[].aiSearchesnumber | nullasks a monthAbsent without aiSearches; null is no reading.
measurements[]ObservedNone.Each row also carries observedAt, method, venue and locale.
costCostUSDNever null. In try mode usd is 0 and maxUsd is what the call would have cost.

Read the result

FieldValueWhat it meansWhat to do
difficulty10, 20, 30, 40, 50, 60, 70About 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)
difficultyLow on a local termThe 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)
searchesnullUnknown, 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)
cpcOver $4.29 for a $149 a month productAbove 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)
trendfallingDemand 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-Key with the same input replays the first answer at $0; with other input it returns 409.

Saved or shared

Each measurement is an immutable Keyword.measured Event; nothing is edited. Measurements are shared across every startup that measures the same phrase and locale.

Emits

Keyword.measured with { term, searches, cpc, competition, difficulty, intent, trend, venue, locale }, one Event per term

Performed by

Analyst

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. maxCost defaults 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-busy and 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

CheckErrorFixThen call
A search engine, answer engine or network the call does not read, such as ChatGPT sent to Keyword.measurevenue-unsupportedPick 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
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

Text
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 valueAlways returnsWhat it is for
rent ledger template{"term":"rent ledger template","searches":1900,"cpc":2.4,"difficulty":18,"intent":"learn","trend":"rising"}1,900 searches a month, $2.40 a click and difficulty 18, every time.
maxCost: 0.0001Refuses with max-costAny priced call with a maxCost of 0.0001 is refused before it spends: the price is above the cap.
rate-limited.testRefuses with rate-limitedAny call naming this hostname answers 429 with Retry-After, so your retry logic runs.
sources-busy.testRefuses with sources-busyAny 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.

TypeScript
await $.Monitor.create({
  verb: 'Keyword.measure',
  input: { keywords: ['rent ledger template'], measures: ['searches', 'cpc'] },
  change: 'volume-change',
  every: '4w',
})

Used by