URL: /capabilities/make-content/monthly-search-content · Id: `page:/capabilities/make-content/monthly-search-content`

# Monthly search content

> On the first of each month, eight new pages for the terms you can win and two refreshed pages that slipped, each published and indexed.

On the first of each month, eight new pages for the terms you can win and two refreshed pages that slipped, each published and indexed.

```text title="Run it · Ask"
Grow organic visits to leaseline.app with 8 new pages this month, spend up to $10, and show me the plan first.
```

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

const result = await $.try.Site.grow({ goal: 'Grow organic visits to leaseline.app with 8 new pages this month, spend up to $10, and show me the plan first.', budget: { amount: 10, currency: 'USD' } })
```

```json title="Run it · Response"
{
  "mode": "try",
  "stored": false,
  "estimate": "About $2.46 per 10 pages",
  "sample": {
    "month": "2026-11",
    "published": "8 of 8",
    "refreshed": "/rent-ledger-template",
    "skipped": "“property management software” (winnability 18)",
    "indexed": "7 of 8 by day 14"
  }
}
```

- **Kind:** Agent task
- **Price:** $2.46 per 10 pages
- **Your time:** 25 minutes
- **Approval:** Goes out on its own
- **Deepens:** [`workflow:publish-content`](https://startupbuilder.dev/workflows/processes/content-marketing/publish-content)

## What you get
- **10 pages** · On the first of each month, eight new pages for the terms you can win and two refreshed pages that slipped, each published and indexed. · [`noun:site`](https://startupbuilder.dev/business/nouns/site)
- `published[]` · Each new page: URL, title, the term it targets and its index state.
- `refreshed[]` · Each revised page and why: “position 14 to page two for 6 weeks; added a filled example”.
- `notDone[]` · Each target not written this month, with the reason, such as winnability 18 or two failed Reviews.
- `decisions[]` · Every choice it made, with its reason: which terms, which refreshes, and what it skipped.

**Connect once:** [Google Search Console](/code/integrations/google-search-console). **How long:** About an hour of agent time across the month; each page then waits days on the index.

## Trigger
It runs on the first of each month at 09:00, and sooner when organic visits sit flat for 8 weeks.

```ts
every('1st at 09:00', ($) => $.Site.grow({ site: 'leaseline.app', pages: 8, refresh: 2 }))
```

## Steps
Your agent does these steps, one row per step; each step’s check is part of its row and its price.

| Step | Verb | Returns | Price | Approval |
| --- | --- | --- | --- | --- |
| 1. Refresh the term list from your seeds | [`Keyword.find`](/code/data/keywords/find) | 300 ideas, the month’s Keyword opportunity map | About 15¢ | Runs on its own |
| 2. Measure volume and cost per click | [`Keyword.measure`](/code/data/keywords/measure) | Searches and CPC for each idea | About 15¢ | Runs on its own |
| 3. Measure difficulty in one batch | [`Keyword.measure`](/code/data/keywords/measure) | A difficulty for each idea, in one call | About 1.2¢ | Runs on its own |
| 4. Read the top 40 results pages | [`Keyword.search`](/code/data/keywords/search) | Who ranks, and the soft slots | About 1.2¢ | Runs on its own |
| 5. Measure who ranks, and your site | [`Domain.benchmark`](/code/data/domains/benchmark) | Authority for each ranking site | About 54¢ | Runs on its own |
| 6. Choose this month’s targets and refreshes | `Content.draft` | Eight terms by winnability and intent, not already covered, and the two pages most worth revising, each with its reason as a Decision | About 2¢ | Runs on its own |
| 7. Research each target | [`Keyword.ask`](/code/data/keywords/ask) | The results page, four engines’ answers and the five pages they cite, per target | About 16¢ | Runs on its own |
| 8. Read the pages the answers cite | [`Page.audit`](/code/data/pages-and-sites/audit-page) | What each cited page says | About 1¢ | Runs on its own |
| 9. Draft and revise each page | `Content.draft` | Ten pages, each a draft and a revision against the Review’s findings | About $1.40 | Runs on its own |
| 10. Publish and link the pages to each other | `Content.publish` | Ten pages live, with internal links between new and old, pinged as an effect of the publish | Free | Goes out on its own |

Calls: `Keyword.find`, then `Keyword.measure`, then `Keyword.search`, then `Domain.benchmark`, then `Content.draft`, then `Keyword.ask`, then `Page.audit`, then `Content.draft`, then `Content.publish`.

About $2.46 per 10 pages, each time it runs, capped at $4.00.

## Example: give it a goal
> Grow organic visits to leaseline.app with 8 new pages this month, spend up to $10, and show me the plan first.

The plan it shows runs 10 steps, priced before it spends anything; Run it above shows the result it returns.

## When something fails

| When | Then |
| --- | --- |
| A target’s page fails Review twice | It moves to `notDone` with the findings, the next term by winnability takes its slot, and the remedy applies. |
| Fewer than 8 terms clear `minWinnability` | It writes fewer pages and says why; it never pads to 8 with terms a new site cannot win. |
| A page goes live and is not indexed in 14 days | Index the site reads it daily and reports the engine’s reason; the contract counts it done with that reason. |
| You call it twice in one month | The second call returns the month’s record: it is idempotent per site and month. |

Each step retries three times, then falls through to the next route; one item's failure lands in `skipped` with its reason, never failing the call. Before a step would cross `maxCost` it stops and returns what it has, with a `resume` link. Each step's idempotency key is the call's key plus the step and the item: `leaseline-site-grow-1-<step>-<item>`.

## How it is graded
A second model reviews each page twice, once for claims and once for what would make it better; Index the site reads each one’s index state for 28 days. It passes when the month’s pages pass both reviews and go live, and each is indexed or carries the engine’s reason. A miss is redone at no charge.

## Change it
| Ask | Compiles to |
| --- | --- |
| Refresh four old pages before writing any new one. | [`Site.grow`](/capabilities/make-content/monthly-search-content) |
| Only write for terms with difficulty under 20. | [`Site.grow`](/capabilities/make-content/monthly-search-content) |
| Write comparison pages against my two nearest rivals this month. | [`Page.answer`](/capabilities/make-content/answer-pages) |

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

export default workflow({
  name: 'site-grow',
  verb: 'Site.grow',
  input: { site: 'string', pages: 'integer', refresh: 'integer', minWinnability: 'integer' },
  trigger: { schedule: '1st at 09:00' },
  maxCost: 4,
  review: [
    { pattern: 'affirmative', rubric: 'targets beat what is already published' },
    { pattern: 'adversarial', rubric: 'every claim resolves to a record or a cited page' },
  ],
  grade: { evaluator: 'claims, then quality, on each page; index state read back', passWhen: 'every page reviewed, live, and indexed or a reason', remedy: 'redo' },
  execute: async (ctx, { site, pages = 8, refresh = 2, minWinnability = 60 }) => {
    const map = await ctx.step('map', () => $.Keyword.map({ seeds: 'saved', site, maxCost: 1 }))
    const plan = await ctx.step('choose', () => $.Content.draft({ kind: 'content-plan', from: map, pages, refresh, minWinnability, maxCost: 0.03 }))
    const written = []
    for (const t of [...plan.targets, ...plan.refreshes]) written.push(await ctx.step(`page:${t.term}`, () => $.Page.answer({ question: t.term, site, update: t.url, maxCost: 0.3 })))
    return { published: written.filter((w) => !w.update), refreshed: written.filter((w) => w.update), decisions: plan.decisions }
  },
})
```

Extend it with `extends: 'Site.grow'` and `base()`, or [compose your own](/code/compose) from these steps.

## Works with
**Plays that order it:** [Category hubs](/plays/category-hubs), [Editorial SEO](/plays/editorial-seo).

It serves [Make content](/capabilities/make-content), and runs in [Launch and search](/strategy/launch-and-search), [Paid](/strategy/paid). It deepens [`workflow:publish-content`](https://startupbuilder.dev/workflows/processes/content-marketing/publish-content).

## For agents

| Fact | Value |
| --- | --- |
| Guide | `Site.grow` |
| Guide approval | `limits` |
| Goal | Grow organic visits to leaseline.app with 8 new pages this month, spend up to $10, and show me the plan first. It may change counts, the order of steps, soft filters and the route a read takes, and it stops early when the goal is met. It never relaxes your words, raises its own budget, answers its own approval, calls a Play or acts outside your standing approval. |
| Trigger | `every('1st at 09:00', ($) => $.Site.grow({ site: 'leaseline.app', pages: 8, refresh: 2 }))` |
| Retry | Three times with backoff, then the next route; the same idempotency key never charges, sends or posts twice |
| Idempotency key | `leaseline-site-grow-1` |
| Lookups | A `find:` input asks unless exactly one record fits |

| Step | Action class | Seat rule that would hold it |
| --- | --- | --- |
| 1. Refresh the term list from your seeds | None | None. |
| 2. Measure volume and cost per click | None | None. |
| 3. Measure difficulty in one batch | None | None. |
| 4. Read the top 40 results pages | None | None. |
| 5. Measure who ranks, and your site | None | None. |
| 6. Choose this month’s targets and refreshes | None | None. |
| 7. Research each target | None | None. |
| 8. Read the pages the answers cite | None | None. |
| 9. Draft and revise each page | None | None. |
| 10. Publish and link the pages to each other | `publish` | None. |

The amounts live once, on the hub ([approvals](https://startupbuilder.dev/agents/approvals)).

| Step | Function | Doer | Checker | Assurance | Inputs |
| --- | --- | --- | --- | --- | --- |
| 1. Refresh the term list from your seeds | `code` | Code | Code checks each result against its declared type | `measured` | `$0.site` |
| 2. Measure volume and cost per click | `code` | Code | Code checks each result against its declared type | `measured` | `$1.terms` |
| 3. Measure difficulty in one batch | `code` | Code | Code checks each result against its declared type | `measured` | `$1.terms` |
| 4. Read the top 40 results pages | `code` | Code | Code checks each result against its declared type | `measured` | `$2.top` |
| 5. Measure who ranks, and your site | `code` | Code | Code checks each result against its declared type | `measured` | `$4.hostnames` |
| 6. Choose this month’s targets and refreshes | `agentic` | The [Content lead](/capabilities/agents/content-lead) agent | A second model grades the output | `graded` | `$1..$5, $0.minWinnability` |
| 7. Research each target | `code` | Code | Code checks each result against its declared type | `measured` | `$6.targets` |
| 8. Read the pages the answers cite | `code` | Code | Code checks each result against its declared type | `measured` | `$7.cited` |
| 9. Draft and revise each page | `agentic` | The [Content lead](/capabilities/agents/content-lead) agent | Affirmative review by a second model: Pass, with the 60-word answer, FAQ and schema in place | `graded` | `$7, $8, offer` |
| 10. Publish and link the pages to each other | `code` | Code | Code reads the result back from where it landed | `measured` | `$9.drafts, $0.site` |

The doer is never the checker, and the weakest assurance on the path is the whole call’s.

## Under the hood

| Kind | What it touches |
| --- | --- |
| Systems | [Sites and content](/code/apps/sites-and-content) |
| Nouns | [`noun:keyword`](https://startupbuilder.dev/business/nouns/keyword), [`noun:domain`](https://startupbuilder.dev/business/nouns/domain), [`noun:content`](https://startupbuilder.dev/business/nouns/content), [`noun:page`](https://startupbuilder.dev/business/nouns/page), [`noun:action`](https://startupbuilder.dev/business/nouns/action) |
| Events | `Keyword.measured`, `Domain.benchmarked`, `Content.drafted`, `Action.evaluated`, `Content.published` |

## Reference

| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `site` | `Hostname` | Required | Your site. |
| `pages` | `integer` | `8` | New pages a month. Eight is the planning number a new site sustains with one review pass each. |
| `refresh` | `integer` | `2` | Old pages revised a month: those that slipped five or more places, or sat on page two for six weeks. |
| `minWinnability` | `integer` | `60` | The score a term needs before it gets a page. |
| `maxCost` | `number` | `4.00` | The most this call spends each time it runs, in USD. It stops before a step would cross it and returns what it has, with a `resume` link. |

Emits `Site.grown`. Refuses with [`max-cost`](/code/refusals#max-cost) before it spends, and with the Data calls and Accounts groups ([refusals](/code/refusals)).