# POST /v1/site/audit

Crawl and audit a whole site; billed per delivered page (sum of enabled checks x pages delivered).

**Credits:** 2 credits per delivered page per enabled check -- billed on what's actually delivered, never a flat rate.

**Timeout:** 600s

## Parameters

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `domain` | string | yes | — | Domain or URL to crawl -- the hostname is extracted from either form. Required, max 253 chars. |
| `max_pages` | integer | yes | — | Maximum number of pages to crawl before stopping. 1-500. |
| `checks` | array | no | ["crawl"] | Which checks to run per crawled page. Optional, default ["crawl"]. Duplicates removed, order preserved. Array of: crawl, lighthouse, accessibility. |
| `device` | string | no | "mobile" | One of: mobile, desktop. Optional, default "mobile". |

## Request

```http
POST /v1/site/audit HTTP/1.1
Authorization: Bearer sof_live_your_key_here
Content-Type: application/json

{
  "domain": "example.com",
  "max_pages": 5,
  "checks": [
    "crawl",
    "lighthouse"
  ],
  "device": "mobile"
}
```

## Response

Wrapped in the standard envelope (id, object, created_at, elapsed_ms, cache, credits) -- documented once on The Contract. The body below is a real envelope with this endpoint's `data` shape; values vary per request, and a `…` marks an array cut short for display.

```json
{
  "id": "site_yxzy3wnefwgpknyyqspxk7i2",
  "request_id": "req_tqubl4c2mbdxnhxxoifm2uncha",
  "object": "site_audit",
  "created_at": "2026-08-09T08:31:45Z",
  "elapsed_ms": 180,
  "cache": "miss",
  "credits": {
    "charged": 12,
    "balance": 9857
  },
  "data": {
    "domain": "example.com",
    "pages_requested": 5,
    "pages_crawled": 4,
    "pages_delivered": 3,
    "credits_charged": 12,
    "pages": [
      {
        "url": "https://example.com/",
        "status": "ok",
        "crawl": {
          "http_status": 200,
          "links": {
            "internal": 2,
            "external": 0
          }
        },
        "lighthouse": {
          "device": "mobile",
          "scores": {
            "performance": 90,
            "accessibility": 91,
            "best_practices": 93,
            "seo": 80
          }
        }
      },
      "…"
    ],
    "summary": {
      "score": {
        "value": 82,
        "band": "excellent",
        "grade": "A"
      },
      "axes": [
        {
          "key": "speed",
          "score": 73,
          "band": "good"
        }
      ],
      "issues": {
        "critical": 2,
        "major": 1,
        "minor": 1
      },
      "broken_links": 1,
      "pages_with_errors": 1
    }
  }
}
```

## Response fields

What each field in `data` (above) means.

| Field | Description |
| --- | --- |
| `data.domain` | The resolved hostname that was crawled. |
| `data.pages_requested` | max_pages from the request, echoed back. |
| `data.pages_crawled` | Number of pages the crawl actually visited (can be less than pages_requested on a smaller site, never more). |
| `data.pages_delivered` | Number of pages that passed every enabled check -- the billing unit. |
| `data.credits_charged` | Actual credits charged for this call: pages_delivered times the enabled checks' per-page cost -- 0 on a replay, since a replay is never re-billed. Always equal to the envelope's own credits.charged. |
| `data.pages` | One row per crawled page, whether delivered or not. |
| `data.pages[].url` | The page URL. |
| `data.pages[].status` | "ok" if every enabled check passed for this page, "error" otherwise -- an error page isn't billed. |
| `data.pages[].crawl` | This page's crawl-check result, when the crawl check is enabled and passed -- same shape as the page/crawl endpoint's data. |
| `data.pages[].crawl.http_status` | HTTP status code for this page. |
| `data.pages[].crawl.links` | Outbound link counts for this page. |
| `data.pages[].crawl.links.internal` | Links pointing at the same site. |
| `data.pages[].crawl.links.external` | Links pointing off-site. |
| `data.pages[].lighthouse` | This page's lighthouse-check result, when the lighthouse check is enabled and passed -- same scores/metrics shape as the page/lighthouse endpoint's data, but without that endpoint's audits/screenshots (kept lean per-page so a multi-page site audit doesn't balloon). |
| `data.pages[].lighthouse.device` | Device profile the audit ran under. |
| `data.pages[].lighthouse.scores` | The same four 0-100 category scores as the standalone lighthouse endpoint. |
| `data.summary` | Site-wide rollup computed across every delivered page. |
| `data.summary.score` | Overall site score. |
| `data.summary.score.value` | 0-100 overall score. |
| `data.summary.score.band` | "excellent" (80+), "good" (50+), or "improve" (below 50). |
| `data.summary.score.grade` | Letter grade A-F derived from value. |
| `data.summary.axes` | Per-dimension scores (speed, accessibility) for whichever checks were enabled. |
| `data.summary.axes[].key` | Which dimension this axis measures, e.g. "speed". |
| `data.summary.axes[].score` | 0-100 score for this axis. |
| `data.summary.axes[].band` | Same excellent/good/improve banding as score.band, for this axis. |
| `data.summary.issues` | Accessibility issues found, bucketed by severity (only populated when the accessibility check is enabled). |
| `data.summary.issues.critical` | Count of critical-severity issues. |
| `data.summary.issues.major` | Count of major-severity issues. |
| `data.summary.issues.minor` | Count of minor-severity issues. |
| `data.summary.broken_links` | Count of internal links returning a non-2xx status, across delivered pages. |
| `data.summary.pages_with_errors` | Count of pages that failed to load cleanly or failed the crawl check. |
