---
title: "Mcp Record A11Y Scan"
method: POST
path: "/v1/mcp/projects/{project_id}/a11y/scan"
tags: ["mcp"]
---

# Mcp Record A11Y Scan

`POST /v1/mcp/projects/{project_id}/a11y/scan`

## Request

- Base URL: `https://api.sonenta.dev`
- URL: `https://api.sonenta.dev/v1/mcp/projects/{project_id}/a11y/scan`
- Auth: none declared

Record ONE mechanical a11y code scan: recap + the keys it SAW (mcp:*, NOT billed).

MCP twin of the core `POST /v1/projects/{id}/a11y/scan`. It exists because the
scanner authenticates with an `mcp:*` API key, and `mcp:*` does not satisfy
`ProjectDeveloper` on the core surface — it 403s there. Widening the scope
mapping instead would have handed `project:write` to EVERY mcp key across the
whole core API, which is a far larger blast radius than one twin route.

Carries the ANALYSIS MARKER (task 1827): `seen_key_uuids` are stamped
`type_analyzed_at` + `type_analyzed_scan_id`, which is what lets
/a11y/detection tell a key you looked at and confirmed as text from a key
nothing ever opened. Retyping alone cannot say that — confirming `text` is a
no-op write. Stamps nothing else: no `updated_at`, no audit row, no
translations. Batch with `scan_id` (see the field); recap counters are SET, so
send the run totals on every batch and re-posting is harmless.

`last_run_at` is stamped server-side. Metered as a read so a local, 0-credit
scan stays 0-credit (spec §4.1).

## Path parameters

- `project_id` string, required

## Request body

- McpA11yScanIn
  - `keys_scanned` integer, required: DISTINCT keys the scan actually SAW at usage sites in the code — not the project total (that is `keys_total` on /a11y/detection). The gap between the two is meaningful: keys that appear nowhere in the code.
  - `keys_classified` integer, required: Keys this run actually retyped. Count the items whose `type_result` came back "changed" — NOT the ones you attempted: a `refused_manual` is a hand-set type the scan must not claim as its own.
  - `tool` string, required
  - `tool_version` string, required
  - `scan_id` string, nullable: OMIT on the first call: a run id is minted and returned. Echo it on every follow-up batch of the SAME scan so all chunks stamp one id. An id that is not this project's current run is refused (422) — better than stamping keys with a stale run.
  - `seen_key_uuids` string[]: Every key this scan SAW at a usage site, whatever the outcome: retyped, CONFIRMED as plain text, or `refused_manual`. Send them all — a confirming retype writes nothing, so this list is the ONLY record that the key was analyzed, and it cannot be reconstructed later. Chunk large projects across several calls sharing one scan_id (this field is capped per call, not per run).

## Response `200`

Successful Response

- object

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/sonenta/apis/sonenta-api.md) · [All operations](https://skmtc.dev/sonenta/apis/sonenta-api/llms.txt) · [OpenAPI document](https://skmtc.dev/sonenta/apis/sonenta-api/revisions/f853ea49fb05?raw)
