URL: /code/data/pages-and-sites/audit-page · Id: `page:/code/data/pages-and-sites/audit-page`

# Page.audit

> Check one page the way a search engine reads it, from title and canonical to structured data and load time, with each fix.

`POST https://api.sb/{your-domain}/pages/audit`

Check one page the way a search engine reads it, from title and canonical to structured data and load time, with each fix.

- **Price:** 2¢ per 100 pages
- **Success rate:** 100% of 14,847 calls
- **Freshness:** Fresh for 1 day
- **Emits:** `Page.audited`
- **Capability:** [Web](https://startupbuilder.dev/code/platform/web)

```text title="Run it · Ask"
What is wrong with leaseline.app/rent-ledger-template the way a search engine reads it? Spend up to 1¢.
```

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

const result = await $.try.Page.audit({ url: 'https://leaseline.app/rent-ledger-template', render: 'full', maxCost: 0.01 })
```

```json title="Run it · Response"
{
  "url": "https://leaseline.app/rent-ledger-template",
  "score": 84,
  "checks": [
    {
      "id": "title-length",
      "pass": true,
      "detail": null,
      "fix": null
    },
    {
      "id": "canonical",
      "pass": true,
      "detail": null,
      "fix": null
    },
    {
      "id": "image-alt",
      "pass": false,
      "detail": "3 images without alt text",
      "fix": "Add alt text that says what each image shows."
    },
    {
      "id": "structured-data",
      "pass": false,
      "detail": "No structured data",
      "fix": "Add FAQ structured data for the questions on the page."
    },
    {
      "id": "ai-crawlers",
      "pass": true,
      "detail": null,
      "fix": null
    }
  ],
  "timings": {
    "lcpMs": 2100,
    "cls": 0.04,
    "inpMs": 140
  },
  "observedAt": "2026-10-27T14:02:11Z",
  "method": "sampled",
  "cost": {
    "usd": 0,
    "maxUsd": 0.005,
    "units": 1,
    "misses": 0
  }
}
```

## Options and prices
| You get | Input that selects it | Price | Success rate |
| --- | --- | --- | --- |
| The page as served | `{"render":"none"}` | About 2¢ per 100 pages | 100% of 14,847 calls |
| The page after its scripts run | `{"render":"js"}` | About 15¢ per 100 pages | 100% of 14,847 calls |
| Fully rendered, with Core Web Vitals | `{"render":"full"}` | About 50¢ per 100 pages | 100% of 14,847 calls |

## Inputs
| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `page` | `Page` | None. | One of your pages. Also served at `/pages/{page}/audit`. Pass this or `url`. |
| `url` | `string` | None. | Any public page, a rival’s included. |
| `render` | `'none' \| 'js' \| 'full'` | `'none'` | How far to render. A page built in the browser needs `js`; `full` adds load timings. |
| `maxAge` | `string` | `'1d'` | An audit younger than this returns at $0. |
| `maxCost` | `number` | `0.05` | 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 | What `null` means |
| --- | --- | --- |
| `url` | `string` | Never null: the URL after redirects. |
| `score` | `number`, in 0 to 100, share of weighted checks passed | Never null. |
| `checks` | `{ id: string; pass: boolean; detail: string \| null; fix: string \| null }[]` | Never null: every check run. `fix` is null on a pass. |
| `timings` | `{ lcpMs: number; cls: number; inpMs: number \| null } \| null` | Null unless `render: full`; `inpMs: null` when the page has no interaction to time. |
| `(page)` | [`Observed`](/code/data/types#observed) | The audit carries `observedAt` and `method`. |
| `cost` | [`Cost`](/code/data/types#cost), in 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 |
| --- | --- | --- | --- |
| `timings.lcpMs` | Over 2,500 | The largest element paints too slowly to rate good. | Compress and size the hero image, and serve it first. (the Core Web Vitals thresholds: 2.5 seconds, 0.1 and 200 milliseconds) |
| `timings.cls` | Over 0.1 | The layout jumps while loading. | Give images and embeds their width and height. (the Core Web Vitals thresholds) |
| `checks` | `ai-crawlers` failing | The AI search crawlers are blocked, so answer engines cannot cite the page. | Allow them in robots.txt; check the page again before you track AI answers. (the AEO playbook) |
| `checks` | `structured-data` failing on a page of questions | Search engines cannot tell the questions and answers apart. | Add FAQ structured data with the exact question wording buyers use. (the AEO playbook) |
| `score` | Under 80 on a page you want indexed | Failing checks are likely holding it back. | Fix the failing checks before the page is published or asked for links. (our planning number) |

## 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-Key` with the same input replays the first answer at $0; with other input it returns 409.

**Saved or shared**

Writes the audit onto your page when `page` is passed; a held page leaves `held` only once its checks pass. Audits of other sites are yours alone.

**Emits**

`Page.audited` with `{ url, score, checks, timings }`

**Performed by**

[Analyst](https://startupbuilder.dev/agents/marketing/analyst)

**Price**

About 2¢ per 100 pages. Free: an audit younger than `maxAge`. `maxCost` defaults to 5¢. Every page of a 48-page site, fully rendered: 48 × 0.5¢ a page is about 24¢.

**Reliability**

The first route succeeds 100% of 14,847 calls. If it fails, the call refuses with `sources-busy` and nothing is spent.

**Freshness**

A reading younger than 1 day returns at $0. Readings about people are never shared across startups.

**Batch and pages**

Up to 1 page a call. It answers in one response.

**Rate**

[The shared rate rules](/code/data#rate): 10 calls a second per key, and an array input counts as one call.

## Refuses when
This call has no refusals of its own. Every data call can also meet [the shared data-call refusals](/code/refusals#data-calls): [`filter-unsupported`](/code/refusals#filter-unsupported), [`input-missing`](/code/refusals#input-missing), [`limit-exceeded`](/code/refusals#limit-exceeded), [`max-cost`](/code/refusals#max-cost), [`rate-limited`](/code/refusals#rate-limited), [`sources-busy`](/code/refusals#sources-busy), [`venue-unsupported`](/code/refusals#venue-unsupported).

## When it waits
Returns in one response; nothing waits.

## Example

**Ask**

```text
What is wrong with leaseline.app/rent-ledger-template the way a search
engine reads it? Spend up to 1¢.
```

**API**

```bash
curl -X POST "https://api.sb/leaseline.app/pages/audit?mode=try" \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://leaseline.app/rent-ledger-template","render":"full","maxCost":0.01}'
```

The response is the one in the Run it window above.

**CLI**

```bash
npx startup-builder do Page.audit --try --data '{"url":"https://leaseline.app/rent-ledger-template","render":"full","maxCost":0.01}'
```

**MCP**

```json
{
  "name": "do",
  "arguments": {
    "mode": "try",
    "code": "return await $.Page.audit({ url: 'https://leaseline.app/rent-ledger-template', render: 'full', maxCost: 0.01 })"
  }
}
```

**SDK**

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

const result = await $.try.Page.audit({
  url: 'https://leaseline.app/rent-ledger-template',
  render: 'full',
  maxCost: 0.01,
})
```

To retry, send the same `Idempotency-Key`: the retry returns the first answer for $0. A new key reads again.

A `sk_test_` key answers these the same way every time, at $0 ([every test value](/code/data#test-values)).

| Test value | Always returns | What it is for |
| --- | --- | --- |
| `maxCost: 0.0001` | Refuses with [`max-cost`](/code/refusals#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`](/code/refusals#rate-limited) | Any call naming this hostname answers 429 with Retry-After, so your retry logic runs. |
| `sources-busy.test` | Refuses with [`sources-busy`](/code/refusals#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.

```ts
await $.Monitor.create({
  verb: 'Page.audit',
  input: { url: 'https://rentrival.example/pricing' },
  change: 'page-change',
  every: '1d',
})
```

## Used by
- **Workflows:** [AI answer audit](/capabilities/check-ai-answers/ai-answer-audit) · [Directory submissions](/capabilities/earn-first-links/directory-submissions) · [Launch kit](/capabilities/make-content/launch-kit) · [Listing check](/capabilities/earn-first-links/listing-check)
- **Agent tasks:** [Answer page per question](/capabilities/make-content/answer-pages) · [Link pitches](/capabilities/earn-first-links/link-pitches) · [Monthly search content](/capabilities/make-content/monthly-search-content) · [Account brief](/capabilities/reach-out/account-brief) · [Personal first touch](/capabilities/reach-out/personal-first-touch)
- **Jobs:** [Check AI answers](/capabilities/check-ai-answers) · [Convert visitors](/capabilities/convert-visitors) · [Make content](/capabilities/make-content) · [Publish and post](/capabilities/publish-and-post) · [Size up competitors](/capabilities/size-up-competitors)

## Related
[`Site.audit`](/code/data/pages-and-sites/audit-site)