Keyword .track
Reads, each day, where your pages rank for every term you track and which AI answers cite you, one row per page, term and day.
- Action class
- None
- Approval
- Runs on its own
- Emits
Keyword.tracked- Price
- 3¢ per 100 reads
Inputs
| Name | Type | Default | Meaning |
|---|---|---|---|
keywords | Keyword[] | every saved Keyword | The terms to track, as Keyword.create saved them. Unset: every Keyword the startup saved. |
venues | ('google-search' | 'bing' | 'chatgpt' | 'perplexity' | 'gemini' | 'claude')[] | ['google-search'] | Where to read. A search engine returns a position; an answer engine returns whether the answer cites your site. |
locale | string | your home Market | The market and language each read is taken in, such as en-US. |
every | '1d' | '1w' | '1d' | How often each term is read. Answer engines read weekly whatever this says, since their answers move slower than results pages. |
depth | number | 100 | How many results deep a search read looks: a page past it reads position:. |
since | string | '30d' | How much history the response returns: 7d, 30d, 90d. Reading history is free. |
maxCost | number | 5.00 | The most tracking may spend in a month, in USD. Over it, the next day’s reads are skipped and the call says so. |
Outputs
| Field | Type | What null means |
|---|---|---|
tracking | { keywords: | Never null: what is tracked from now on, and when the next read runs. |
history | RankHistory[] | Never null: one row per page, term, Venue and day over since; empty on the first call, before any read. |
history[].page | string | null | None of your pages appeared in the first depth results, or the answer cited none of them. |
history[].position | number | null, in rank, 1 is the top | Not in the first depth results, or an answer engine, which has no positions. |
history[].feature | 'organic' | 'featured-snippet' | 'ai-overview' | 'local-pack' | 'answer' | null | Your page did not appear. |
history[].cited | boolean | null | A search engine row: only an answer engine cites. |
history[].change | number | null, in positions since the read before | The first read of this page for this term, or an answer engine row. |
history[] | Observed | Each row also carries observedAt, method, venue and locale. |
cost | Cost, in USD | Never null: what today’s reads spent. In try mode usd is 0 and maxUsd is a month at this setting. |
Effects
- Action class
None: no approval rule names it, so it runs within its seat’s Permissions and Limits
- Modes
try: Runs one read of each term now and returns its rows, unsaved; nothing is scheduled and nothing is billed.send: Saves the setting and returns the stored history at once; each scheduled read emitsKeyword.trackedwith its rows.do: Saves the setting, runs today’s read, and returns the history with today’s rows in it.
- Idempotency
Only with a key: the same
Idempotency-Keyand input return the first Action, and the same key with other input returns409. Calling it again with otherkeywordsorvenueschanges what is tracked from the next read; history is never rewritten.- Saved or shared
The tracking setting and every RankHistory row, kept for as long as the startup exists. Results pages are shared readings, as on
Keyword.search; which of your pages ranked is yours alone.- Emits
Keyword.trackedwith{ keyword,rows } - Performed by
- Price
About 3¢ per 100 reads. At most 40¢ per 100 reads. Free: history reads, a term no page of yours targets yet, until it is saved.
On each Venue
It acts on your own records and reaches no Venue, so every account answers the same way.
What try does
Runs every check the call meets and returns the record as it would be; nothing is stored, sent or posted.
Refuses when
| Check | Error | Fix | Then call |
|---|---|---|---|
A data call whose price for what you asked is above its maxCost, checked before anything is spent | max-cost | Lower limit, or raise maxCost to the price the refusal’s detail quotes. | Organization.find |
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 |
The max-cost payload
{
"type": "https://startupbuilder.dev/errors/max-cost" ,
"title": "The call would spend more than its cap",
"status": 422,
"detail": "A data call whose price for what you asked is above its `maxCost`, checked before anything is spent." ,
"fix": "Lower `limit`, or raise `maxCost` to the price the refusal’s detail quotes." ,
"next": {
"verb": "Organization.find",
"input": {
"question": "US property managers with 20 to 400 units who still track rent in spreadsheets",
"limit": 25,
"maxCost": 0.05
}
},
"instance": "https://api.sb/leaseline.app/actions/action_1"
}When it waits
In send mode it returns the stored history at once, before the next read runs, and Keyword.tracked delivers the result.
await $.Watch.create({
selector: 'Keyword.tracked',
url: 'https://leaseline.app/hooks/sb'
})Example
The second call, a week on: the history alone, free, for the one term the readout flagged.
How did the rent ledger template page move on Google this week?{
"api": "https://api.sb/leaseline.app" ,
"tracking": {
"keywords": 1,
"venues": [
"google-search"
],
"every": "1d",
"nextReadAt": "2026-11-03T06:00:00Z"
},
"history": [
{
"keyword": "https://api.sb/leaseline.app/keywords/keyword_Rl1tE5mP3x" ,
"term": "rent ledger template",
"page": "https://leaseline.app/rent-ledger-template" ,
"venue": "google-search",
"locale": "en-US",
"day": "2026-10-27",
"position": 14,
"feature": "organic",
"cited": null,
"change": 3,
"observedAt": "2026-10-27T06:10:00Z" ,
"method": "sampled"
},
{
"keyword": "https://api.sb/leaseline.app/keywords/keyword_Rl1tE5mP3x" ,
"term": "rent ledger template",
"page": "https://leaseline.app/rent-ledger-template" ,
"venue": "google-search",
"locale": "en-US",
"day": "2026-11-02",
"position": 9,
"feature": "organic",
"cited": null,
"change": 5,
"observedAt": "2026-11-02T06:10:00Z" ,
"method": "sampled"
}
],
"cost": {
"usd": 0,
"maxUsd": 0,
"units": 0,
"misses": 0
},
"action": {
"operation": "Keyword.track",
"mode": "try",
"status": "done"
}
}To retry, send the same Idempotency-Key: the retry returns the first answer for $0. A new key reads again.
Test values
A sk_test_ key answers these the same way every time, at $0 (every test value).
| Test value | Always returns | What it is for |
|---|---|---|
keywords: | {"history": | Always position 14 on Google, so the step that fires on page one can be tested with a known miss. |
Used by
None.