URL: /code/data/domains/verify · Id: `page:/code/data/domains/verify`

# Domain.verify

> Check your domain’s DNS and certificate, whether Google and Bing read its sitemap, and each key page’s index state with its fix.

`POST https://api.sb/{your-domain}/domains/verify`

Check your domain’s DNS and certificate, whether Google and Bing read its sitemap, and each key page’s index state with its fix.

- **Price:** Free
- **Success rate:** 99.8% of 45,746 calls
- **Freshness:** Read fresh each call
- **Emits:** `Domain.verified`
- **Capability:** [Domains](https://startupbuilder.dev/products/platform/domains)

```text title="Run it · Ask"
Is leaseline.app verified on Google and Bing, and is the rent ledger template page indexed yet? Spend nothing.
```

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

const result = await $.try.Domain.verify({ domain: 'domain_ut1ZlxY4Z1', pages: ['https://leaseline.app/', 'https://leaseline.app/rent-ledger-template'], maxCost: 0 })
```

```json title="Run it · Response"
{
  "domain": "domain_ut1ZlxY4Z1",
  "dns": {
    "ok": true,
    "fix": null
  },
  "certificate": {
    "ok": true,
    "fix": null
  },
  "searchEngines": [
    {
      "venue": "google-search",
      "verified": true,
      "sitemap": {
        "submitted": 48,
        "indexed": 9,
        "lastRead": "2026-10-25T03:10:00Z",
        "errors": 0
      }
    },
    {
      "venue": "bing",
      "verified": true,
      "sitemap": null
    }
  ],
  "pages": [
    {
      "url": "https://leaseline.app/",
      "state": "indexed",
      "reason": null,
      "lastCrawl": "2026-10-24T11:02:00Z",
      "canonical": "https://leaseline.app/",
      "robots": "allowed"
    },
    {
      "url": "https://leaseline.app/rent-ledger-template",
      "state": "crawled-not-indexed",
      "reason": "Crawled, currently not indexed",
      "lastCrawl": "2026-10-23T08:41:00Z",
      "canonical": "https://leaseline.app/rent-ledger-template",
      "robots": "allowed"
    }
  ],
  "lagDays": 2,
  "observedAt": "2026-10-27T14:02:11Z",
  "method": "reported"
}
```

## Inputs
| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `domain` | `Domain` | Required | Your Domain id. Also served at `/domains/{domain}/verify`, as the hub defines it. |
| `pages` | `string[]` | `the Site’s key Pages` | Pages to inspect, up to 2,000 a day per site, shared with every other tool on that property. |

## Outputs
| Field | Type | What `null` means |
| --- | --- | --- |
| `domain` | `Domain` | Never null: with its `state` after the checks. |
| `dns` | `{ ok: boolean; fix: string \| null }` | Never null. `fix` is null when `ok`. |
| `certificate` | `{ ok: boolean; fix: string \| null }` | Never null. `fix` is null when `ok`. |
| `searchEngines` | `{ venue: 'google-search' \| 'bing'; verified: boolean; sitemap: { submitted: number; indexed: number; lastRead: string \| null; errors: number } \| null }[]` | Never null. `sitemap: null` means no sitemap was read yet. |
| `pages` | `{ url: string; state: IndexState; reason: string \| null; lastCrawl: string \| null; canonical: string \| null; robots: 'allowed' \| 'blocked' \| null }[]` | Never null: one row per page inspected. |
| `pages[].state` | `'indexed' \| 'discovered-not-indexed' \| 'crawled-not-indexed' \| 'duplicate' \| 'not-seen' \| 'excluded'` | Never null. |
| `pages[].reason` | `string \| null` | Null when indexed; otherwise the engine’s own wording. |
| `pages[].lastCrawl` | `string \| null`, in ISO 8601 | Never crawled. |
| `pages[].canonical` | `string \| null` | The engine has not chosen one yet. |
| `lagDays` | `number`, in days | Never null: how far behind the engine’s own report runs, about 2 days. |
| `observedAt` | `string`, in ISO 8601 | Never null. |
| `method` | `'reported' \| 'sampled'` | Never null. Google reads `reported`; Bing has no reporting API, so its fields are read by a browser agent and read `sampled`. |

## Read the result
| Field | Value | What it means | What to do |
| --- | --- | --- | --- |
| `pages[].state` | `not-seen` | The engine has never found the page. | Confirm it is in the sitemap, link it from 2 indexed pages, and earn one followed outside link. (the index-state playbook) |
| `pages[].state` | `discovered-not-indexed` | Found, not crawled yet: low priority on a new domain. | Link it from the home and pricing pages; request indexing for key pages only. (the index-state playbook) |
| `pages[].state` | `crawled-not-indexed` | Crawled and judged not worth indexing yet. | Merge thin pages and add a fact only you have. Do not request again until the page changed. (the index-state playbook) |
| `pages` | Key pages indexed | Under 80% by week 2, or under 100% by week 4, means discovery is the problem, not content. | Fix discovery before you write another page. (our planning numbers) |
| `pages` | Over 30% `crawled-not-indexed` at week 6 | A quality problem across the site. | Pause publishing and fix the thin pages first. (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 every check and returns the results; nothing is written onto the Domain.
- `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 each check onto the Domain; a failed check moves a live Domain to `degraded`, and a degraded canonical Domain opens an Incident.

**Emits**

`Domain.verified` with `{ domain, dns, certificate, searchEngines, pages }`

**Performed by**

[Reliability Engineer](https://startupbuilder.dev/agents/engineering/reliability-engineer)

**Price**

Free. Free: every check, read from your own Search Console.

**Reliability**

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

**Freshness**

Every call reads fresh. Readings about people are never shared across startups. The source runs 2 days behind.

**Batch and pages**

Up to 2,000 pages a day per site. 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
| Check | Error | Fix | Then call |
| --- | --- | --- | --- |
| The call needs an account you have not connected, such as your Search Console property or your Meta ad account | [`account-not-connected`](/code/refusals#account-not-connected) | Connect the account with [`Integration.connect`](https://startupbuilder.dev/business/nouns/integration/connect), approve it at the connectUrl, then send the call again. | [`Integration.connect`](/code/integrations/calls/connect) |
| The account’s own daily or per-minute cap is used up, such as 2,000 page checks a day per Search Console property | [`quota-reached`](/code/refusals#quota-reached) | Retry after the reset time the response gives; nothing was sent or spent. | [`Action.retry`](https://startupbuilder.dev/business/nouns/action/retry) |

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).

```json
{
  "type": "https://startupbuilder.dev/errors/account-not-connected",
  "title": "Account not connected",
  "status": 409,
  "detail": "The call needs an account you have not connected, such as your Search Console property or your Meta ad account.",
  "fix": "Connect the account with Integration.connect, approve it at the connectUrl, then send the call again.",
  "next": {
    "verb": "Integration.connect",
    "input": {
      "system": "ads",
      "venue": "facebook",
      "scope": "startup",
      "grants": [
        "ads_management",
        "ads_read"
      ]
    }
  },
  "instance": "https://api.sb/leaseline.app/actions/action_1"
}
```

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

## Example

**Ask**

```text
Is leaseline.app verified on Google and Bing, and is the rent ledger
template page indexed yet? Spend nothing.
```

**API**

```bash
curl -X POST "https://api.sb/leaseline.app/domains/verify?mode=try" \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain":"domain_ut1ZlxY4Z1","pages":["https://leaseline.app/","https://leaseline.app/rent-ledger-template"],"maxCost":0}'
```

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

**CLI**

```bash
npx startup-builder do Domain.verify --try --data '{"domain":"domain_ut1ZlxY4Z1","pages":["https://leaseline.app/","https://leaseline.app/rent-ledger-template"],"maxCost":0}'
```

**MCP**

```json
{
  "name": "do",
  "arguments": {
    "mode": "try",
    "code": "return await $.Domain.verify({ domain: 'domain_ut1ZlxY4Z1', pages: ['https://leaseline.app/', 'https://leaseline.app/rent-ledger-template'], maxCost: 0 })"
  }
}
```

**SDK**

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

const result = await $.try.Domain.verify({
  domain: 'domain_ut1ZlxY4Z1',
  pages: ['https://leaseline.app/', 'https://leaseline.app/rent-ledger-template'],
  maxCost: 0,
})
```

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: 'Domain.verify',
  input: { domain: 'domain_ut1ZlxY4Z1' },
  change: 'index-change',
  every: '1d',
})
```

## Used by
- **Workflows:** [AI answer audit](/capabilities/check-ai-answers/ai-answer-audit) · [Index the site](/capabilities/get-indexed/index-the-site)
- **Agent tasks:** [Answer page per question](/capabilities/make-content/answer-pages)
- **Jobs:** [Get indexed](/capabilities/get-indexed) · [Measure](/capabilities/measure)

## Related
[`Domain.benchmark`](/code/data/domains/benchmark) · [`Domain.findCompetitors`](/code/data/domains/find-competitors) · [`Domain.findLinks`](/code/data/domains/find-links)