---
title: "Start Requirement Test"
method: POST
path: "/marketing/requirement-tuning/requirements/{requirement_id}/tests"
tags: ["marketing-requirement-tuning"]
---

# Start Requirement Test

`POST /marketing/requirement-tuning/requirements/{requirement_id}/tests`

## Request

- Base URL: `https://api.kobaltlabs.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.kobaltlabs.com/marketing/requirement-tuning/requirements/{requirement_id}/tests`
- Auth: none declared

Judge this draft against the reviewed assets, and answer with the test to poll.

202 with the rows already written, not 200 with the answer: this is twenty-odd Opus calls, one per
asset with no shared attachment to cache, and a request that waited for them would hold a pooled
connection and a proxy timeout for minutes. The items come back `pending`, so the surface's first
poll already knows the denominator rather than watching it grow.

Everything the fan-out needs is resolved here and the session released before the first call — see
`helpers/requirement_tests`.

## Path parameters

- `requirement_id` string, uuid, required

## Request body

- StartRequirementTestRequest: Try this draft against the assets a reviewer has already ruled on.
  - `draft` RequirementDraft, required: A wording and scope nobody has saved yet. Every field is required rather than optional-with-carry-forward, unlike the PATCH this precedes. A test has to judge *something specific*, and a payload that omitted the scope would silently inherit the live rule's — so a reviewer who had narrowed the collateral types on screen would be shown a result for the scope they were replacing.
    - `requirement_text` string, required
    - `judging_guidance` string, nullable
    - `trigger_terms` string[]
    - `collateral_types` MarketingCollateralType[], nullable
    - `marketing_team_ids` string[], nullable
  - `file_ids` string[], nullable
  - `include_unreviewed` boolean

## Response `202`

Successful Response

- RequirementTest: One test: the draft, the assets, and what came of it.
  - `test_id` string, uuid, required
  - `requirement_id` string, uuid, required
  - `base_revision_id` string, uuid, required
  - `status` 'running' | 'succeeded' | 'partial' | 'failed', required: Where a test is, and how it ended. `RUNNING` is the state the request returns in: the row is written and the fan-out dispatched, so the surface has something to poll from the first tick rather than a spinner with nothing behind it. `PARTIAL` is its own member rather than a flag on `SUCCEEDED`. A test where two of twenty-two assets never answered is a real result worth keeping — eighteen answers is the answer — but a suggestion built from it has to know it is reading an incomplete picture, and a reviewer deciding whether their edit worked must not be shown eighteen verdicts as if they were twenty-two.
  - `requirement_text` string, required
  - `judging_guidance` string, nullable
  - `trigger_terms` string[]
  - `collateral_types` string[], nullable
  - `marketing_team_ids` string[], nullable
  - `created` string, date-time, required
  - `started_at` string, date-time, nullable
  - `finished_at` string, date-time, nullable
  - `error` string, nullable
  - `created_by_email` string, nullable
  - `model` string, required
  - `llm_calls` integer
  - `totals` RequirementTestTotals: The sentence the surface leads with, as numbers. `agree + disagree == reviewed_judged`, and `unreviewed`, `undetermined`, `not_considered`, `unfinished` and `omitted` all sit outside both — so a reader can always tell what the ratio is a ratio *of*. That is the one arithmetic property this surface cannot get wrong: a bench claiming twenty of twenty-two while silently counting the nine nobody reviewed is worse than no bench.
    - `total` integer
    - `reviewed_judged` integer
    - `agree` integer
    - `disagree` integer
    - `unreviewed` integer
    - `undetermined` integer
    - `not_considered` integer
    - `unfinished` integer
    - `pending` integer
    - `omitted` integer
  - `items` RequirementTestItem[]
    - `item_id` string, uuid, required
    - `file_id` string, uuid, required
    - `filename` string, nullable
    - `document_id` string, uuid, nullable
    - `collateral_type` string, nullable
    - `state` 'pending' | 'judged' | 'not_considered' | 'unfinished', required: Why one collateral in a test has the answer it has. Separate from `RequirementCheckOutcome`, which says what the *judge* decided. These say whether the judge ran at all, and the distinction is the point of the surface: a rule that no longer fires because the reviewer's new trigger terms exclude the asset has not been judged lenient, it has been scoped away — and a reviewer editing trigger terms needs to see that rather than a `PASSED` that looks like the model changing its mind.
    - `outcome` 'passed' | 'failed' | 'not_determined': What happened when one requirement met one piece of collateral. Stored rather than derived. "We checked 34 requirements and 3 failed" cannot be reconstructed from the risks alone: a requirement that produced no finding is indistinguishable in `document_risks` from one that was never evaluated, and that difference is the whole feature. **`NOT_DETERMINED` is why the model is asked to answer for every rule** rather than only to report failures. Silence has two meanings — "the collateral satisfies this" and "I cannot tell from what the collateral shows" — and a panel that reads the first from the absence of a finding claims a pass the model never gave. The judging contract therefore makes each rule land in exactly one of these three, and a rule the model omits altogether lands here too.
    - `excluded_by` ApplicabilityExclusion[]
    - `attachment` 'document' | 'text': How the collateral was put in front of the judge. Recorded because a verdict reached from the text layer is not the same claim as one reached from the rendered page, and afterwards the two are indistinguishable. A `PASSED` on a rule about a logo, a disclosure's prominence or where a footnote sits means nothing if the model only ever saw extracted text — and `prompt_fingerprint` cannot tell them apart, because the system prompt, the tool and the schema are identical either way.
    - `reason` string, nullable
    - `short_text` string, nullable
    - `explanation` string, nullable
    - `remediation` string, nullable
    - `collateral_clause` string, nullable
    - `severity` string, nullable
    - `citations` object[], nullable
    - `baseline_outcome` 'passed' | 'failed' | 'not_determined': What happened when one requirement met one piece of collateral. Stored rather than derived. "We checked 34 requirements and 3 failed" cannot be reconstructed from the risks alone: a requirement that produced no finding is indistinguishable in `document_risks` from one that was never evaluated, and that difference is the whole feature. **`NOT_DETERMINED` is why the model is asked to answer for every rule** rather than only to report failures. Silence has two meanings — "the collateral satisfies this" and "I cannot tell from what the collateral shows" — and a panel that reads the first from the absence of a finding claims a pass the model never gave. The judging contract therefore makes each rule land in exactly one of these three, and a rule the model omits altogether lands here too.
    - `baseline_verdict` 'flagged' | 'dismissed_over_flagged' | 'dismissed_accepted' | 'dismissed_other' | 'unreviewed': The reviewer's decision on a production risk, derived from its stored columns. `SUPERSEDED` is deliberately **not** a member: obsoletion is a property of the run, not a decision by a reviewer, and a risk can be dismissed and later superseded. Collapsing them would throw away the reviewer's decision — which is the thing this dashboard exists to compare against. It travels as its own boolean.
    - `baseline_risk_id` string, uuid, nullable
    - `baseline_run_id` string, uuid, nullable
    - `fired` boolean, nullable
    - `duration_ms` integer, nullable
    - `error` string, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/kobaltlabs/apis/fastapi.md) - [All operations](https://skmtc.dev/kobaltlabs/apis/fastapi/llms.txt) - [OpenAPI document](https://skmtc.dev/kobaltlabs/apis/fastapi/revisions/8587332d5e43?raw)
