# Site Audit

Source: https://ceyo.ai/docs/signal/site-audit

### Site Audit

Read current website health, pages, categories, and findings for a project or location. Audit execution is scheduled by Signal; the public API does not start audits manually.

**Scope:** Project

### Get Site Audit overview

`GET /projects/{project_id}/site_audit`

Returns the current score state, page and finding totals, blocking details, and latest audit status.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Project identifier. |

#### Response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | uuid |  | Resolved project identifier. |
| `site_audit` | SiteAuditOverview |  | Current Site Audit state and totals. |

#### SiteAuditOverview

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `score_result` | object |  | Current, refreshing, or unavailable score state. |
| `blocking_details` | object \| null |  | Public crawler-blocking diagnostics. |
| `pages` | object |  | Inventory and latest audit-status counts. |
| `findings` | object |  | Open finding counts by severity and category. |
| `latest_step` | object \| null |  | Latest scheduled Site Audit step and timing. |
| `last_page_audited_at` | datetime \| null |  | Latest page audit completion time. |

#### ScoreResult

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `state` | current \| refreshing \| unavailable |  | Availability state for the score. |
| `current` | boolean |  | Whether the score represents the latest completed audit. |
| `refreshing` | boolean |  | Whether a newer scheduled audit is processing. |
| `reason` | string \| null |  | Reason a current score is unavailable. |
| `blocking_audit` | object \| null |  | Audit currently preventing a current score. |
| `score` | Score \| null |  | Latest available scope or page score. |

#### Score

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `overall_score` | number |  | Overall score from 0 to 100. |
| `technical_score` | number |  | Technical pillar score when available. |
| `content_score` | number |  | Content pillar score when available. |
| `category_scores` | object |  | Scores keyed by normalized category. |
| `confidence` | number |  | Score confidence when available. |
| `coverage` | number |  | Audited page coverage when available. |
| `issue_counts` | object |  | Finding counts represented by the score. |
| `deltas` | object |  | Changes from the previous score. |
| `score_version` | string |  | Scoring implementation version. |
| `check_version` | string |  | Site Audit check-set version. |
| `scored_at` | datetime |  | Time the score was calculated. |

#### BlockingAudit

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `status` | string |  | Blocking audit status. |
| `pagespeed_status` | string \| null |  | PageSpeed collection status. |
| `error_code` | string \| null |  | Stable audit failure code. |
| `completed_at` | datetime \| null |  | Audit completion time. |
| `created_at` | datetime |  | Audit creation time. |

#### BlockingDetails

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `provider` | string |  | Detected blocking provider. |
| `title` | string |  | Public blocking summary. |
| `description` | string |  | Public explanation of the block. |
| `recommendation` | string |  | Recommended remediation. |
| `http_status` | integer \| null |  | Observed HTTP status. |
| `diagnostic` | object \| null |  | Sanitized provider diagnostic. |
| `allowlist_ips` | string\[\] |  | Signal crawler IPs to allow. |
| `user_agent` | string |  | Signal crawler user agent. |
| `affected_url` | URL \| null |  | Sanitized blocked URL. |
| `last_seen_at` | datetime \| null |  | Most recent blocking observation. |

#### OverviewPages

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `total` | integer |  | All known pages. |
| `active` | integer |  | Pages in the active inventory. |
| `removed` | integer |  | Pages removed from the active inventory. |
| `by_discovery_source` | object |  | Active page counts keyed by discovery source. |
| `latest_audit_statuses` | object |  | Latest page-run counts keyed by status. |

#### FindingSummary

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `total` | integer |  | Total visible findings. |
| `open` | integer |  | Open visible findings. |
| `by_status` | object |  | Counts keyed by finding status. |
| `open_by_severity` | object |  | Open counts keyed by severity. |
| `open_by_category` | object |  | Open counts keyed by normalized category. |

#### LatestStep

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `status` | string |  | Scheduled Site Audit step status. |
| `started_at` | datetime \| null |  | Step start time. |
| `completed_at` | datetime \| null |  | Step completion time. |

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/{project_id}/site_audit' \
  --header "Authorization: Bearer ${SIGNAL_API_KEY}"
```

```json
{
  "project_id": "7c51e5f2-0bad-4e09-82de-fc876c81f531",
  "site_audit": {
    "score_result": {
      "state": "current",
      "current": true,
      "refreshing": false,
      "reason": null,
      "blocking_audit": null,
      "score": {
        "overall_score": 86.4,
        "technical_score": 90.2,
        "content_score": 83.3,
        "category_scores": {
          "performance": 78.0,
          "crawlability": 94.0,
          "security": 96.0,
          "structured_data": 82.0,
          "content_quality": 81.0,
          "content_structure": 88.0,
          "meta_onpage": 91.0,
          "aeo_readiness": 76.0
        },
        "confidence": 0.96,
        "coverage": 1.0,
        "issue_counts": {"critical": 0, "high": 2, "medium": 5},
        "deltas": {"overall_score": 2.1},
        "score_version": "v1",
        "check_version": "v1",
        "scored_at": "2026-08-09T09:30:00Z"
      }
    },
    "blocking_details": null,
    "pages": {
      "total": 42,
      "active": 42,
      "removed": 0,
      "by_discovery_source": {"sitemap": 42},
      "latest_audit_statuses": {"succeeded": 42}
    },
    "findings": {
      "total": 18,
      "open": 7,
      "by_status": {"open": 7, "resolved": 11},
      "open_by_severity": {"high": 2, "medium": 5},
      "open_by_category": {"meta_onpage": 3, "crawlability": 4}
    },
    "latest_step": {
      "status": "succeeded",
      "started_at": "2026-08-09T09:25:00Z",
      "completed_at": "2026-08-09T09:31:00Z"
    },
    "last_page_audited_at": "2026-08-09T09:30:45Z"
  }
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": [
      {
        "field": "name",
        "message": "must be present"
      }
    ],
    "request_id": "req_01K1GP6J8QQFZ4D2B6C5A9V3TS"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `401` | invalid\_api\_key |  | The API key is absent or invalid. |
| `403` | forbidden \| site\_audit\_unavailable |  | The key lacks access or Site Audit is disabled. |
| `404` | not\_found |  | The scope or requested Site Audit resource was not found. |
| `422` | visibility\_unavailable |  | Project-level visibility is unavailable for this project. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Get Site Audit categories

`GET /projects/{project_id}/site_audit/categories`

Returns current category scores and the metrics shown in the Site Audit technical and content views.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Project identifier. |

#### Response body

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | uuid |  | Resolved project identifier. |
| `state` | string |  | Current score state. |
| `current` | boolean |  | Whether scores represent the latest completed audit. |
| `refreshing` | boolean |  | Whether a newer scheduled audit is processing. |
| `categories` | object \| null |  | Score keyed by Site Audit category. |
| `category_metrics` | object |  | Metrics and sections keyed by category. |
| `blocking_audit` | object \| null |  | Audit currently refreshing or blocking scoring. |
| `score_version` | string \| null |  | Scoring implementation version. |
| `check_version` | string \| null |  | Site Audit check-set version. |

#### CategoryMetrics

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `generated_at` | datetime \| null |  | Metric generation time. |
| `total_pages` | integer |  | Pages represented by these metrics. |
| `categories` | object<string, CategoryMetric> |  | Metrics keyed by normalized Site Audit category. |

#### CategoryMetric

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `score` | number \| null |  | Category score. |
| `issue_count` | integer |  | Findings in this category. |
| `affected_pages` | integer |  | Distinct affected pages. |
| `severity_counts` | object |  | Finding counts keyed by severity. |
| `metrics` | Metric\[\] |  | Category-specific headline metrics. |
| `sections` | object\[\] |  | Category-specific detail sections. |

#### Metric

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `key` | string |  | Stable metric key. |
| `label` | string |  | Display label. |
| `value` | number \| string \| null |  | Metric value. |
| `suffix` | string \| null |  | Optional display suffix. |
| `tone` | string \| null |  | Optional display tone. |

#### BlockingAudit

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `status` | string |  | Blocking audit status. |
| `pagespeed_status` | string \| null |  | PageSpeed collection status. |
| `error_code` | string \| null |  | Stable audit failure code. |
| `completed_at` | datetime \| null |  | Audit completion time. |
| `created_at` | datetime |  | Audit creation time. |

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/{project_id}/site_audit/categories' \
  --header "Authorization: Bearer ${SIGNAL_API_KEY}"
```

```json
{
  "project_id": "7c51e5f2-0bad-4e09-82de-fc876c81f531",
  "state": "current",
  "current": true,
  "refreshing": false,
  "categories": {"crawlability": 94.0, "content_quality": 81.0},
  "category_metrics": {
    "generated_at": "2026-08-09T09:30:00Z",
    "total_pages": 42,
    "categories": {
      "crawlability": {
        "score": 94.0,
        "issue_count": 4,
        "affected_pages": 3,
        "severity_counts": {"medium": 4},
        "metrics": [],
        "sections": []
      }
    }
  },
  "score_version": "v1",
  "check_version": "v1",
  "blocking_audit": null
}
```

> **Example scope**
>
> Category maps can contain every supported category; the example shows representative entries to keep it readable.

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": [
      {
        "field": "name",
        "message": "must be present"
      }
    ],
    "request_id": "req_01K1GP6J8QQFZ4D2B6C5A9V3TS"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `401` | invalid\_api\_key |  | The API key is absent or invalid. |
| `403` | forbidden \| site\_audit\_unavailable |  | The key lacks access or Site Audit is disabled. |
| `404` | not\_found |  | The scope or requested Site Audit resource was not found. |
| `422` | visibility\_unavailable |  | Project-level visibility is unavailable for this project. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### List Site Audit pages

`GET /projects/{project_id}/site_audit/pages`

Lists the current page inventory with score, latest run, and finding summaries.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Project identifier. |

#### Query parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `page` | integer | Optional; Default: 1 | 1-based page number. |
| `per_page` | integer | Optional; Default: 20 | Records per page. Maximum: 50. |
| `q` | string | Optional | URL or path search. Maximum: 200 characters. |
| `status` | active \| removed | Optional; Default: active | Inventory status filter. |
| `discovery_source` | seed \| sitemap \| crawl \| provider \| manual | Optional | Discovery source filter. |
| `audit_status` | pending \| running \| succeeded \| failed | Optional | Latest page audit status. |
| `sort` | last\_audited\_at \| url \| score \| issues | Optional; Default: last\_audited\_at | Sort field. |
| `direction` | asc \| desc | Optional; Default: desc | Sort direction. |

#### Response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | uuid |  | Resolved project identifier. |
| `pages` | Page\[\] |  | Matching audited pages. |
| `pagination` | Pagination |  | Offset pagination metadata. |

#### Page

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Audited page identifier. |
| `url` | URL |  | Sanitized page URL. |
| `normalized_url` | URL |  | Canonical URL used to identify the page. |
| `host` | string |  | Page hostname. |
| `path` | string |  | Page path. |
| `status` | active \| removed |  | Inventory status. |
| `discovery_source` | seed \| sitemap \| crawl \| provider \| manual |  | How Signal discovered the page. |
| `first_discovered_at` | datetime |  | Time Signal first discovered the page. |
| `last_discovered_at` | datetime |  | Most recent discovery time. |
| `last_audited_at` | datetime \| null |  | Most recent completed audit time. |
| `latest_run` | object \| null |  | Latest page audit status and public fetch details. |
| `score_result` | object |  | Current page score state and score. |
| `open_findings` | object |  | Open finding total and severity counts. |

#### LatestRun

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `status` | pending \| running \| succeeded \| failed |  | Page audit status. |
| `http_status` | integer \| null |  | Observed HTTP status. |
| `final_url` | URL \| null |  | Sanitized final URL after redirects. |
| `fetch_ms` | integer \| null |  | Origin fetch duration in milliseconds. |
| `error_code` | string \| null |  | Stable failure code. |
| `error_message` | string \| null |  | Safe public failure message. |
| `completed_at` | datetime \| null |  | Completion time. |

#### ScoreResult

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `state` | current \| refreshing \| unavailable |  | Availability state for the score. |
| `current` | boolean |  | Whether the score represents the latest completed audit. |
| `refreshing` | boolean |  | Whether a newer scheduled audit is processing. |
| `reason` | string \| null |  | Reason a current score is unavailable. |
| `blocking_audit` | object \| null |  | Audit currently preventing a current score. |
| `score` | Score \| null |  | Latest available scope or page score. |

#### PageScore

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `overall_score` | number |  | Page score from 0 to 100. |
| `category_scores` | object |  | Page scores keyed by normalized category. |
| `issue_counts` | object |  | Page finding counts represented by the score. |

#### OpenFindings

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `total` | integer |  | Total open customer-visible findings. |
| `by_severity` | object |  | Open counts keyed by severity. |

#### Pagination

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `page` | integer |  | Current 1-based page. |
| `per_page` | integer |  | Records returned per page. |
| `total` | integer |  | Total matching records. |
| `total_pages` | integer |  | Total available pages. |

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/{project_id}/site_audit/pages?page=1&per_page=20' \
  --header "Authorization: Bearer ${SIGNAL_API_KEY}"
```

```json
{
  "project_id": "7c51e5f2-0bad-4e09-82de-fc876c81f531",
  "pages": [{
    "id": "eb67fc48-e109-42cc-bbeb-1b2b8042bd14",
    "url": "https://example.com/",
    "normalized_url": "https://example.com/",
    "host": "example.com",
    "path": "/",
    "status": "active",
    "discovery_source": "sitemap",
    "first_discovered_at": "2026-07-01T10:00:00Z",
    "last_discovered_at": "2026-08-09T09:20:00Z",
    "last_audited_at": "2026-08-09T09:30:00Z",
    "latest_run": {
      "status": "succeeded",
      "http_status": 200,
      "final_url": "https://example.com/",
      "fetch_ms": 218,
      "error_code": null,
      "error_message": null,
      "completed_at": "2026-08-09T09:30:00Z"
    },
    "score_result": {
      "state": "current",
      "current": true,
      "refreshing": false,
      "reason": null,
      "blocking_audit": null,
      "score": {
        "overall_score": 92.0,
        "category_scores": {"crawlability": 96.0, "meta_onpage": 88.0},
        "issue_counts": {"medium": 1}
      }
    },
    "open_findings": {"total": 1, "by_severity": {"medium": 1}}
  }],
  "pagination": {"page": 1, "per_page": 20, "total": 42, "total_pages": 3}
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": [
      {
        "field": "name",
        "message": "must be present"
      }
    ],
    "request_id": "req_01K1GP6J8QQFZ4D2B6C5A9V3TS"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `400` | invalid\_filter |  | A filter value is not supported. |
| `401` | invalid\_api\_key |  | The API key is absent or invalid. |
| `403` | forbidden \| site\_audit\_unavailable |  | The key lacks access or Site Audit is disabled. |
| `404` | not\_found |  | The scope or requested Site Audit resource was not found. |
| `422` | validation\_failed \| visibility\_unavailable |  | The input is invalid or project-level visibility is unavailable. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Get Site Audit page

`GET /projects/{project_id}/site_audit/pages/{page_id}`

Returns one page with its current score, latest audit, and open finding counts.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Project identifier. |
| `page_id` | uuid | Required | Site Audit page identifier. |

#### Response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | uuid |  | Resolved project identifier. |
| `page` | Page |  | Requested audited page. |

#### Page

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Audited page identifier. |
| `url` | URL |  | Sanitized page URL. |
| `normalized_url` | URL |  | Canonical URL used to identify the page. |
| `host` | string |  | Page hostname. |
| `path` | string |  | Page path. |
| `status` | active \| removed |  | Inventory status. |
| `discovery_source` | seed \| sitemap \| crawl \| provider \| manual |  | How Signal discovered the page. |
| `first_discovered_at` | datetime |  | Time Signal first discovered the page. |
| `last_discovered_at` | datetime |  | Most recent discovery time. |
| `last_audited_at` | datetime \| null |  | Most recent completed audit time. |
| `latest_run` | object \| null |  | Latest page audit status and public fetch details. |
| `score_result` | object |  | Current page score state and score. |
| `open_findings` | object |  | Open finding total and severity counts. |

#### LatestRun

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `status` | pending \| running \| succeeded \| failed |  | Page audit status. |
| `http_status` | integer \| null |  | Observed HTTP status. |
| `final_url` | URL \| null |  | Sanitized final URL after redirects. |
| `fetch_ms` | integer \| null |  | Origin fetch duration in milliseconds. |
| `error_code` | string \| null |  | Stable failure code. |
| `error_message` | string \| null |  | Safe public failure message. |
| `completed_at` | datetime \| null |  | Completion time. |

#### ScoreResult

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `state` | current \| refreshing \| unavailable |  | Availability state for the score. |
| `current` | boolean |  | Whether the score represents the latest completed audit. |
| `refreshing` | boolean |  | Whether a newer scheduled audit is processing. |
| `reason` | string \| null |  | Reason a current score is unavailable. |
| `blocking_audit` | object \| null |  | Audit currently preventing a current score. |
| `score` | Score \| null |  | Latest available scope or page score. |

#### PageScore

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `overall_score` | number |  | Page score from 0 to 100. |
| `category_scores` | object |  | Page scores keyed by normalized category. |
| `issue_counts` | object |  | Page finding counts represented by the score. |

#### OpenFindings

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `total` | integer |  | Total open customer-visible findings. |
| `by_severity` | object |  | Open counts keyed by severity. |

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/{project_id}/site_audit/pages/{page_id}' \
  --header "Authorization: Bearer ${SIGNAL_API_KEY}"
```

```json
{
  "project_id": "7c51e5f2-0bad-4e09-82de-fc876c81f531",
  "page": {
    "id": "eb67fc48-e109-42cc-bbeb-1b2b8042bd14",
    "url": "https://example.com/",
    "normalized_url": "https://example.com/",
    "host": "example.com",
    "path": "/",
    "status": "active",
    "discovery_source": "sitemap",
    "first_discovered_at": "2026-07-01T10:00:00Z",
    "last_discovered_at": "2026-08-09T09:20:00Z",
    "last_audited_at": "2026-08-09T09:30:00Z",
    "latest_run": {
      "status": "succeeded",
      "http_status": 200,
      "final_url": "https://example.com/",
      "fetch_ms": 218,
      "error_code": null,
      "error_message": null,
      "completed_at": "2026-08-09T09:30:00Z"
    },
    "score_result": {
      "state": "current",
      "current": true,
      "refreshing": false,
      "reason": null,
      "blocking_audit": null,
      "score": {
        "overall_score": 92.0,
        "category_scores": {"crawlability": 96.0, "meta_onpage": 88.0},
        "issue_counts": {"medium": 1}
      }
    },
    "open_findings": {"total": 1, "by_severity": {"medium": 1}}
  }
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": [
      {
        "field": "name",
        "message": "must be present"
      }
    ],
    "request_id": "req_01K1GP6J8QQFZ4D2B6C5A9V3TS"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `401` | invalid\_api\_key |  | The API key is absent or invalid. |
| `403` | forbidden \| site\_audit\_unavailable |  | The key lacks access or Site Audit is disabled. |
| `404` | not\_found |  | The scope or requested Site Audit resource was not found. |
| `422` | visibility\_unavailable |  | Project-level visibility is unavailable for this project. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### List Site Audit findings

`GET /projects/{project_id}/site_audit/findings`

Lists individual findings or groups open findings by check when grouped=true.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Project identifier. |

#### Query parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `page` | integer | Optional; Default: 1 | 1-based page number. |
| `per_page` | integer | Optional; Default: 20 | Records per page. Maximum: 50. |
| `grouped` | boolean | Optional; Default: false | Return open findings grouped by check. Grouped responses are not paginated. |
| `limit` | integer | Optional; Default: 20 | Grouped result limit from 1 to 20. |
| `q` | string | Optional | Finding text search. Maximum: 200 characters. |
| `status` | open \| resolved \| ignored \| all | Optional; Default: open | Status filter for individual findings. |
| `severity` | critical \| high \| medium \| low \| info | Optional | Severity filter. |
| `category` | SiteAuditCategory | Optional | performance, crawlability, security, structured\_data, content\_quality, content\_structure, meta\_onpage, or aeo\_readiness. |
| `area` | technical \| content | Optional | Finding area filter. |
| `page_id` | uuid | Optional | Return findings for one audited page. |

#### Individual response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | uuid |  | Resolved project identifier. |
| `grouped` | false |  | Indicates an individual finding response. |
| `findings` | Finding\[\] |  | Paginated matching findings. |
| `summary` | object |  | Scope-wide finding totals and status counts. |
| `pagination` | Pagination |  | Offset pagination metadata. |

#### Grouped response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | uuid |  | Resolved project identifier. |
| `grouped` | true |  | Indicates a grouped response. |
| `truncated` | boolean |  | Whether the grouped result was bounded before all matches were represented. |
| `groups` | FindingGroup\[\] |  | Up to 20 groups, ordered by severity. |

#### Finding

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Finding identifier. |
| `code` | string |  | Stable Site Audit check code. |
| `category` | string |  | Normalized technical or content category. |
| `severity` | critical \| high \| medium \| low \| info |  | Current severity. |
| `status` | open \| resolved \| ignored |  | Current finding status. |
| `target_kind` | string |  | Type of resource affected by the finding. |
| `target` | string \| null |  | Affected page or target. |
| `title` | string |  | Finding title. |
| `description` | string \| null |  | Observed condition. |
| `recommendation` | string \| null |  | Recommended remediation. |
| `occurrences` | integer |  | Number of times the finding has been observed. |
| `first_seen_at` | datetime |  | Time the finding was first observed. |
| `last_seen_at` | datetime |  | Most recent observation time. |
| `resolved_at` | datetime \| null |  | Time the finding resolved. |

#### FindingGroup

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Shared Site Audit check code. |
| `title` | string |  | Representative title. |
| `description` | string \| null |  | Representative description. |
| `recommendation` | string \| null |  | Representative recommendation. |
| `severity` | critical \| high \| medium \| low \| info |  | Highest severity in the group. |
| `category` | SiteAuditCategory |  | Normalized category. |
| `pillar` | technical \| content |  | Top-level Site Audit area. |
| `occurrences` | integer |  | Occurrences represented by the bounded result set. |
| `affected_pages` | integer |  | Distinct affected pages represented by the result set. |
| `findings_count` | integer |  | Matching findings represented by the result set. |
| `findings_truncated` | boolean |  | Whether the findings array is incomplete. |
| `findings` | Finding\[\] |  | Up to 50 matching findings. |

#### FindingSummary

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `total` | integer |  | Total visible findings. |
| `open` | integer |  | Open visible findings. |
| `by_status` | object |  | Counts keyed by finding status. |
| `open_by_severity` | object |  | Open counts keyed by severity. |
| `open_by_category` | object |  | Open counts keyed by normalized category. |

#### Pagination

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `page` | integer |  | Current 1-based page. |
| `per_page` | integer |  | Records returned per page. |
| `total` | integer |  | Total matching records. |
| `total_pages` | integer |  | Total available pages. |

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/{project_id}/site_audit/findings?grouped=true&severity=high' \
  --header "Authorization: Bearer ${SIGNAL_API_KEY}"
```

```json
{
  "project_id": "7c51e5f2-0bad-4e09-82de-fc876c81f531",
  "grouped": true,
  "truncated": false,
  "groups": [{
    "code": "missing_title",
    "title": "Missing title",
    "severity": "high",
    "category": "meta_onpage",
    "pillar": "content",
    "occurrences": 1,
    "affected_pages": 1,
    "findings_count": 1,
    "findings_truncated": false,
    "findings": [{
      "id": "3c77a8fa-d0ea-49f6-a49e-a6ae23e33ef3",
      "code": "missing_title",
      "category": "meta_onpage",
      "severity": "high",
      "status": "open",
      "target_kind": "page",
      "target": "https://example.com/about",
      "title": "Missing title",
      "description": "The page does not define a title.",
      "recommendation": "Add a concise, descriptive title.",
      "occurrences": 1,
      "first_seen_at": "2026-08-01T10:00:00Z",
      "last_seen_at": "2026-08-09T09:30:00Z",
      "resolved_at": null
    }]
  }]
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": [
      {
        "field": "name",
        "message": "must be present"
      }
    ],
    "request_id": "req_01K1GP6J8QQFZ4D2B6C5A9V3TS"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `400` | invalid\_filter |  | A filter value is not supported. |
| `401` | invalid\_api\_key |  | The API key is absent or invalid. |
| `403` | forbidden \| site\_audit\_unavailable |  | The key lacks access or Site Audit is disabled. |
| `404` | not\_found |  | The scope or requested Site Audit resource was not found. |
| `422` | validation\_failed \| visibility\_unavailable |  | The input is invalid or project-level visibility is unavailable. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Get Site Audit finding

`GET /projects/{project_id}/site_audit/findings/{finding_id}`

Returns one finding and up to 100 recent observations.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Project identifier. |
| `finding_id` | uuid | Required | Site Audit finding identifier. |

#### Response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | uuid |  | Resolved project identifier. |
| `finding` | FindingDetail |  | Requested finding. |
| `observations` | Observation\[\] |  | Up to 100 observations, newest first. |

#### FindingDetail

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Finding identifier. |
| `code` | string |  | Stable Site Audit check code. |
| `category` | string |  | Normalized technical or content category. |
| `severity` | critical \| high \| medium \| low \| info |  | Current severity. |
| `status` | open \| resolved \| ignored |  | Current finding status. |
| `target_kind` | string |  | Type of resource affected by the finding. |
| `target` | string \| null |  | Affected page or target. |
| `title` | string |  | Finding title. |
| `description` | string \| null |  | Observed condition. |
| `recommendation` | string \| null |  | Recommended remediation. |
| `occurrences` | integer |  | Number of times the finding has been observed. |
| `first_seen_at` | datetime |  | Time the finding was first observed. |
| `last_seen_at` | datetime |  | Most recent observation time. |
| `resolved_at` | datetime \| null |  | Time the finding resolved. |
| `evidence` | object |  | Sanitized evidence supporting the finding. |

#### Observation

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `outcome` | detected \| resolved |  | Finding state during this observation. |
| `severity` | critical \| high \| medium \| low \| info |  | Observed severity. |
| `evidence` | object |  | Sanitized evidence captured during this observation. |
| `observed_at` | datetime |  | Observation time. |

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/{project_id}/site_audit/findings/{finding_id}' \
  --header "Authorization: Bearer ${SIGNAL_API_KEY}"
```

```json
{
  "project_id": "7c51e5f2-0bad-4e09-82de-fc876c81f531",
  "finding": {
    "id": "3c77a8fa-d0ea-49f6-a49e-a6ae23e33ef3",
    "code": "missing_title",
    "category": "meta_onpage",
    "severity": "high",
    "status": "open",
    "target_kind": "page",
    "target": "https://example.com/about",
    "title": "Missing title",
    "description": "The page does not define a title.",
    "recommendation": "Add a concise, descriptive title.",
    "evidence": {},
    "occurrences": 2,
    "first_seen_at": "2026-08-01T10:00:00Z",
    "last_seen_at": "2026-08-09T09:30:00Z",
    "resolved_at": null
  },
  "observations": []
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": [
      {
        "field": "name",
        "message": "must be present"
      }
    ],
    "request_id": "req_01K1GP6J8QQFZ4D2B6C5A9V3TS"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `401` | invalid\_api\_key |  | The API key is absent or invalid. |
| `403` | forbidden \| site\_audit\_unavailable |  | The key lacks access or Site Audit is disabled. |
| `404` | not\_found |  | The scope or requested Site Audit resource was not found. |
| `422` | visibility\_unavailable |  | Project-level visibility is unavailable for this project. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Get Site Audit settings

`GET /projects/{project_id}/site_audit/settings`

Returns sitemap and crawl-discovery settings.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Project identifier. |

#### Response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | uuid |  | Resolved project identifier. |
| `settings` | SiteAuditSettings |  | Current discovery settings. |

#### SiteAuditSettings

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `sitemap_urls` | URL\[\] |  | Up to 20 sitemap URLs on the audited website host. |
| `crawl_include_patterns` | string\[\] |  | Path patterns included during crawl discovery. |
| `crawl_exclude_patterns` | string\[\] |  | Path patterns excluded during crawl discovery. |

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/{project_id}/site_audit/settings' \
  --header "Authorization: Bearer ${SIGNAL_API_KEY}"
```

```json
{
  "project_id": "7c51e5f2-0bad-4e09-82de-fc876c81f531",
  "settings": {
    "sitemap_urls": ["https://example.com/sitemap.xml"],
    "crawl_include_patterns": ["/docs/*"],
    "crawl_exclude_patterns": ["/private/*"]
  }
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": [
      {
        "field": "name",
        "message": "must be present"
      }
    ],
    "request_id": "req_01K1GP6J8QQFZ4D2B6C5A9V3TS"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `401` | invalid\_api\_key |  | The API key is absent or invalid. |
| `403` | forbidden \| site\_audit\_unavailable |  | The key lacks access or Site Audit is disabled. |
| `404` | not\_found |  | The scope or requested Site Audit resource was not found. |
| `422` | visibility\_unavailable |  | Project-level visibility is unavailable for this project. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Update Site Audit settings

`PATCH /projects/{project_id}/site_audit/settings`

Updates sitemap and crawl patterns without starting an audit or inventory synchronization.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Project identifier. |

> **PATCH semantics**
>
> Omitted fields remain unchanged. Send an empty array to clear a setting. Sitemap URLs are limited to 20; each crawl-pattern array is limited to 50 strings, with at most 500 characters per string.

#### JSON body

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `sitemap_urls` | URL\[\] | Optional | Up to 20 sitemap URLs on the audited website host. |
| `crawl_include_patterns` | string\[\] | Optional | Path patterns included during crawl discovery. |
| `crawl_exclude_patterns` | string\[\] | Optional | Path patterns excluded during crawl discovery. |

#### Response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | uuid |  | Resolved project identifier. |
| `settings` | SiteAuditSettings |  | Updated discovery settings. |

#### SiteAuditSettings

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `sitemap_urls` | URL\[\] |  | Up to 20 sitemap URLs on the audited website host. |
| `crawl_include_patterns` | string\[\] |  | Path patterns included during crawl discovery. |
| `crawl_exclude_patterns` | string\[\] |  | Path patterns excluded during crawl discovery. |

#### Request and response

```curl
curl --request PATCH \
  --url 'https://api.signal.ceyo.ai/v1/projects/{project_id}/site_audit/settings' \
  --header "Authorization: Bearer ${SIGNAL_API_KEY}" \
  --header 'Content-Type: application/json' \
  --data '{"sitemap_urls":["https://example.com/sitemap.xml"],"crawl_exclude_patterns":["/private/*"]}'
```

```json
{
  "project_id": "7c51e5f2-0bad-4e09-82de-fc876c81f531",
  "settings": {
    "sitemap_urls": ["https://example.com/sitemap.xml"],
    "crawl_include_patterns": ["/docs/*"],
    "crawl_exclude_patterns": ["/private/*"]
  }
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": [
      {
        "field": "name",
        "message": "must be present"
      }
    ],
    "request_id": "req_01K1GP6J8QQFZ4D2B6C5A9V3TS"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `401` | invalid\_api\_key |  | The API key is absent or invalid. |
| `403` | forbidden \| site\_audit\_unavailable \| superadmin\_managed |  | The key lacks access, Site Audit is disabled, or writes are centrally managed. |
| `404` | not\_found |  | The scope or requested Site Audit resource was not found. |
| `422` | validation\_failed \| visibility\_unavailable |  | The input is invalid or project-level visibility is unavailable. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |
