> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coverbase.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Zero Touch Assessments API

> Launch lightweight, no-outreach triage runs, rank a portfolio by the resulting score, and read a single run's composite and evidence.

<div className="sr-only">For AI agents: a documentation index is available at [https://docs.coverbase.com/llms.txt](https://docs.coverbase.com/llms.txt). This page is also available in markdown by appending .md to the URL.</div>

A Zero Touch Assessment is a lightweight triage run assembled entirely from open-source research, with no document request, no questionnaire and no vendor contact. It produces a single 0–100 **Zero Touch Score** so a portfolio can be ranked before real review effort is spent.

For the concepts behind the score, see [Zero Touch Assessments](/user-guides/zero-touch-assessments).

<Note>
  Every endpoint below is behind the `zero-touch-assessments` module. If it is not enabled for your organization, these routes return `404`. Contact your Coverbase representative to have it turned on.
</Note>

All endpoints are org-scoped to the API key. See [API conventions](/conventions) for authentication, IDs, timestamps, idempotency, and the error envelope.

## Launch a run

<ParamField path="method" type="POST">
  `POST /v1/zero_touch_assessment`
</ParamField>

Starts a Zero Touch Assessment for one vendor.

### Request body

<ParamField body="vendor_id" type="string" required>
  The vendor ID (`cbvndr_...`) to assess.
</ParamField>

<ParamField body="service_ids" type="string[]">
  Restrict the run to specific services. Omit to run against the vendor as a whole.
</ParamField>

<ParamField body="template_key" type="string">
  Switch your organization's Zero Touch scope to this template before launching. Omit to launch against whatever is already configured, which is what a caller with no opinion means.

  <Warning>
    This is an organization-level setting, not a per-run one. Passing it changes what **every subsequent run** evaluates, including runs launched from the dashboard.
  </Warning>
</ParamField>

### Responses

| Status | Meaning                                                                |
| ------ | ---------------------------------------------------------------------- |
| `200`  | The run was created. The body is the run summary.                      |
| `400`  | The vendor ID is unknown or belongs to another organization.           |
| `409`  | No Zero Touch control sets are designated for your organization.       |
| `404`  | The module is not enabled.                                             |
| `400`  | The vendor ID is unknown, or `template_key` is not a shipped template. |

<Note>
  Organizations are provisioned the shipped templates on first use, so a `409` is unusual. It means every template has since been un-designated or archived, which is a deliberate act that provisioning will not undo.
</Note>

## Launch in bulk

<ParamField path="method" type="POST">
  `POST /v1/zero_touch_assessment/bulk`
</ParamField>

Starts a run for many vendors in one call. Vendor IDs are deduplicated.

<ParamField body="vendor_ids" type="string[]" required>
  Vendor IDs to assess.
</ParamField>

**Partial success is the normal outcome and returns `200`.** The response separates the two:

<ResponseField name="created" type="object[]">
  Run summaries for the vendors that launched.
</ResponseField>

<ResponseField name="failed" type="object[]">
  One entry per vendor that could not launch, each with `vendor_id` and `error`.
</ResponseField>

One archived or unknown vendor in a selection of forty does not cost you the other thirty-nine. Always read `failed`. A `200` does not mean every vendor launched.

## Read a run

<ParamField path="method" type="GET">
  `GET /v1/zero_touch_assessment/{assessment_id}`
</ParamField>

Returns the single-page result: who the vendor is, the composite, the components behind it, control-set coverage, open issues with their evidence, and every source cited.

<ResponseField name="result.profile" type="object | null">
  The researched company profile: `company_name`, `url`, `description`, `industry`, `use_case`, `services`, `hq_location`, and `confidence`, which is how sure the run is it profiled the right company. `null` until the research completes, which happens shortly after launch and independently of the controls.
</ResponseField>

<ResponseField name="result.profile.risk_tier" type="string | null">
  `critical`, `high`, `moderate`, `low` or `minimal`, with `risk_tier_confidence`, a one-line `risk_tier_reasoning` and the full `risk_tier_detailed_reasoning`.

  <Warning>
    This is its own scale, not your organization's risk levels. Those are calibrated against a completed questionnaire; this is judged from public evidence. Do not map one onto the other.
  </Warning>
</ResponseField>

<ResponseField name="result.composite_score" type="number | null">
  The Zero Touch Score, 0–100, higher is healthier. `null` when nothing has resolved yet.
</ResponseField>

<ResponseField name="result.components" type="object[]">
  One entry per component (`controls`, `financial_health`, `security`, `registration`, `people`), each with `status` (`scored` / `unavailable` / `disabled`), its own `score`, the `configured_weight` set for your org, and the `weight` it actually carried.
</ResponseField>

<ResponseField name="result.sources" type="object[]">
  Every place the run drew evidence from, grouped by `kind`: `web`, `registry`, `infrastructure`, `document`.
</ResponseField>

<Warning>
  **A component with no data is excluded from the composite, never counted as zero.** Weights renormalise over whatever resolved, which is why `weight` and `configured_weight` differ. A vendor scored on one component is not comparable to a vendor scored on three, so read `components` before ranking on `composite_score`.
</Warning>

A run that has not finished is scored on the fly, so this endpoint is useful before completion. Only completion and an explicit rescore persist the snapshot.

## Rank a portfolio

<ParamField path="method" type="GET">
  `GET /v1/zero_touch_assessment/leaderboard`
</ParamField>

Returns one row per vendor, its most recent run, ranked by composite score. Supports `limit`, `offset` and `search`.

Ranking is computed across the whole organization before the page is sliced, so paging through the leaderboard gives a true global ranking. Unscored runs sort last rather than as zero.

## List and choose templates

<ParamField path="method" type="GET">
  `GET /v1/zero_touch_assessment/templates`
</ParamField>

<ParamField path="method" type="POST">
  `POST /v1/zero_touch_assessment/template`
</ParamField>

The `GET` returns the shipped templates with their control counts and domains, plus `active_template_key`, the one your runs currently evaluate. It is `null` when your organization runs a control set of its own, or when more than one shipped template is designated; both are states to show rather than resolve arbitrarily.

The `POST` takes `{"template_key": "..."}` and points future runs at that template. It only ever re-designates control sets this module provisioned, so a set designated for you by Coverbase is never disturbed.

## Re-source a run's vendor profile

<ParamField path="method" type="POST">
  `POST /v1/zero_touch_assessment/{assessment_id}/reprofile`
</ParamField>

Re-researches the company behind the run and replaces the profile on it. Separate from re-scoring because they answer different questions: a correction that repoints a run at the right company leaves the score valid while the profile still describes somebody else.

Re-scoring deliberately carries an existing profile forward, so this is the only call that refreshes it.

## Re-score a run

<ParamField path="method" type="POST">
  `POST /v1/zero_touch_assessment/{assessment_id}/rescore`
</ParamField>

Recomputes the composite against today's inputs and re-applies the configured score push, without re-running the controls. Use this when the financial or security inputs have refreshed, or after changing your scoring configuration.

## Review a run

<ParamField path="method" type="POST">
  `POST /v1/zero_touch_assessment/{assessment_id}/review`
</ParamField>

Records a decision against a run without changing what it was built on.

<ParamField body="action" type="string" required>
  `approved`, `escalated`, or `note`.
</ParamField>

<ParamField body="note" type="string">
  Free text, up to 4000 characters.
</ParamField>

Escalating records the judgement; it does not launch a full assessment.

## Correct a run's inputs

<ParamField path="method" type="POST">
  `POST /v1/zero_touch_assessment/{assessment_id}/correct`
</ParamField>

<ParamField body="instruction" type="string" required>
  The correction in plain language, up to 2000 characters.
</ParamField>

The instruction is read into a fixed set of correctable fields (the vendor's website and name), applied, and the run re-scored against them. Returns the re-scored run.

<Note>
  An instruction that yields no concrete change still returns `200` and still writes an audit entry with an empty `changes` array. Read `changes` to see what actually moved.
</Note>

## Read the audit trail

<ParamField path="method" type="GET">
  `GET /v1/zero_touch_assessment/{assessment_id}/audit`
</ParamField>

Every review action on a run, in the order taken.

<ResponseField name="data[].action" type="string">
  `approved`, `escalated`, `corrected`, `rescored`, or `note`.
</ResponseField>

<ResponseField name="data[].instruction" type="string | null">
  The reviewer's own words, stored verbatim, for `corrected` entries.
</ResponseField>

<ResponseField name="data[].changes" type="object[]">
  One entry per field the action changed, each with `field`, `before` and `after`. Empty for approvals, notes, and corrections that resolved to nothing.
</ResponseField>

<ResponseField name="data[].composite_before / composite_after" type="number | null">
  The Zero Touch Score either side of the action.
</ResponseField>

<Warning>
  The trail is append-only. There is no update or delete endpoint, by design. A Zero Touch Score can be pushed onto a vendor's risk score, so the record of who changed the inputs behind it must be one nobody can rewrite.
</Warning>

## Read and set scoring configuration

<ParamField path="method" type="GET">
  `GET /v1/zero_touch_assessment/config`
</ParamField>

Returns whether the module can run (`is_ready`), the control sets in scope, and the current scoring configuration.

<ParamField path="method" type="POST">
  `POST /v1/zero_touch_assessment/config`
</ParamField>

Sets what counts toward the score and where the score goes.

<ParamField body="include_financial_health" type="boolean">
  Whether the Financial Health Score is a component.
</ParamField>

<ParamField body="include_security_score" type="boolean">
  Whether the outside-in security rating is a component.
</ParamField>

<ParamField body="control_weight" type="integer">
  Relative weight of the control evaluation. Weights are relative, not percentages. They renormalise over whichever components resolve.
</ParamField>

<ParamField body="financial_health_weight" type="integer">
  Relative weight of the financial health component.
</ParamField>

<ParamField body="security_weight" type="integer">
  Relative weight of the security component.
</ParamField>

<ParamField body="include_registration" type="boolean">
  Whether corporate-registration standing is a component.
</ParamField>

<ParamField body="registration_weight" type="integer">
  Relative weight of the registration component.
</ParamField>

<ParamField body="include_people" type="boolean">
  Whether the leadership dossier is a component.
</ParamField>

<ParamField body="people_weight" type="integer">
  Relative weight of the people component.
</ParamField>

<Warning>
  The `people` component deducts for adverse-media findings **only when screening actually ran**. When it did not, the component scores leadership visibility alone and says so in its `qualifier`. Do not read its score as evidence of a clean record. See [People Intelligence](/products/people-intelligence).
</Warning>

<ParamField body="score_target" type="string">
  Where a completed run writes its score: `none`, `vendor_inherent`, `vendor_residual`, `service_inherent`, or `service_residual`.
</ParamField>

Only the axis you choose is written, so a Zero Touch run can never overwrite an inherent score your team set deliberately from an intake questionnaire. A vendor-scoped run configured for a service target writes nothing and reports why in `result.push_skipped_reason`.

<ParamField body="push_risk_tier_to_vendor" type="boolean">
  Whether a completed run writes its **risk tier** onto vendor inherent risk. Off by default.
</ParamField>

The tier is a triage read from public evidence, so an organization that has not asked for it never finds a vendor's inherent risk moved by one. Even when enabled, the push stands down if `score_target` is already `vendor_inherent`, because two writers on one field would let run order decide the vendor's risk, and reports why in `result.risk_tier_push_skipped_reason`.

## Listing runs

There is no separate list endpoint, deliberately. A Zero Touch run is an assessment. Use the existing assessment endpoints with the `is_zero_touch` filter:

* `is_zero_touch: true` returns triage runs only
* `is_zero_touch: false` returns full assessments only
* omitted returns both

A second read path would be free to drift from the first.

## Via MCP

The same data is available to agents through the Coverbase MCP server:

```
query_assessments(kind="zero_touch", assessment_id="cbqsrw_...")
query_assessments(kind="zero_touch_leaderboard", limit=25)
query_assessments(kind="zero_touch_audit", assessment_id="cbqsrw_...")
```

Both are read-only and org-scoped to the authenticated user. See the [MCP overview](/mcp/overview).
