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

# Policy changes

> Change the risk policy of an environment safely: preview new thresholds against retained answers, publish with an expected policy version and a reason, read the immutable history, restore an earlier version, and run policy evaluations.

Every change to the [settings](https://docs.noresponse.org/reference/settings.md) of an environment creates a new `policy_version`. NoResponse keeps each version as an immutable snapshot. These routes help you change the risk policy without surprises:

| Route | What it does |
| - | - |
| `POST /v1/workspaces/{workspace_id}/settings/{environment}/preview` | Shows how new thresholds would have decided recent retained answers. It changes nothing. |
| `PATCH /v1/workspaces/{workspace_id}/settings/{environment}` | Publishes a change. With `expected_policy_version`, it publishes only if nobody changed the settings since you read them. |
| `GET /v1/workspaces/{workspace_id}/settings/{environment}/history` | Lists the immutable versions, newest first. |
| `POST /v1/workspaces/{workspace_id}/settings/{environment}/restore` | Publishes an earlier version again, as a new version. |
| `/v1/workspaces/{workspace_id}/evaluations/{environment}` | Creates, lists, reads, cancels and publishes policy evaluations. |

`environment` is `live` or `test`. A key reaches only its own workspace and environment.

## Who can call them

| Action | Scopes |
| - | - |
| Read the history, preview, and create, read or cancel an evaluation | `settings` or `management` |
| Change `retention_days` only | `settings` or `management` |
| Change `flows` or `refused_sources`, restore a version, or publish an evaluation | `settings` and `policy_publish` together, or `management` |

A key without these scopes answers 403 [`workspace_forbidden`](https://docs.noresponse.org/problems/workspace-forbidden). A member of the workspace needs a role that grants the action. Otherwise the answer is 403 [`role_forbidden`](https://docs.noresponse.org/problems/role-forbidden).

## A safe change, step by step

**1. Read the settings in force**

`GET /v1/workspaces/{workspace_id}/settings/{environment}` answers the settings and their `policy_version`. Keep that number.

**2. Preview the new thresholds**

Send the thresholds you want to the preview route. Check which decisions would change.

**3. Publish with the version you read**

Send the change with `expected_policy_version` and a `change_reason`.

**4. Check the history**

Read the history to check the new version. If outcomes regress, restore the previous version.

## Preview new thresholds

The preview applies proposed thresholds to the first answers that NoResponse still retains, at their exact recorded scores. It uses the same threshold and hold rules as production. It changes no account, outcome or evidence, and it does not publish anything.

```json title="POST /v1/workspaces/{workspace_id}/settings/live/preview" theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "expected_policy_version": 7,
  "from": "2026-10-01T00:00:00Z",
  "to": "2026-10-29T00:00:00Z",
  "limit": 1000,
  "flows": { "account_creation": { "review": 70, "block": 90 } }
}
```

**expected\_policy\_version** `integer` *required*

The `policy_version` that you read. When the settings changed since then, the preview answers 409 [`policy_version_conflict`](https://docs.noresponse.org/problems/policy-version-conflict).

**from, to** `string` *required*

The window of answers to compare, as RFC 3339 times. The window is at most 31 days.

**limit** `integer`

How many answers to compare, newest first, from 1 to 5,000. The default is 1,000.

**flows** `object` *required*

For each flow, the `step_up`, `review` and `block` to try, or `inherits_account_creation: true` for `login` or `purchase`. Send `"block": null` to try with blocking off.

The preview only compares thresholds. It does not simulate changes to admission, sources, depth or retention.

```json title="200 preview" theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "scope": "retained_first_answer_thresholds",
  "policy_version": 7,
  "from": "2026-10-01T00:00:00Z",
  "to": "2026-10-29T00:00:00Z",
  "retention_from": "2025-10-29T00:00:00Z",
  "coverage": {
    "scanned": 1000,
    "eligible": 982,
    "missing_score": 12,
    "unsupported": 6,
    "truncated": true,
    "complete_retained_window": false,
    "historical_population_known": false
  },
  "changed": 41,
  "unchanged": 941,
  "transitions": [
    { "from": "step_up", "to": "review", "count": 29 },
    { "from": "review", "to": "block", "count": 12 }
  ]
}
```

**changed, unchanged** `integer`

How many eligible answers the proposed thresholds decide differently, and how many they decide the same.

**transitions** `object[]`

Each change of decision, `from` and `to` one of `allow`, `step_up`, `review`, `block` or `held`, with its `count`. At most 25 entries.

**coverage** `object`

What the preview could see. `scanned` counts the answers that NoResponse read, and `eligible` the answers that it compared. `missing_score` had no recorded score. `unsupported` are older answers without the snapshot that the comparison needs. `truncated` is `true` when `limit` stopped the scan before the end of the window. `complete_retained_window` is `true` when the preview compared every retained answer in the window, with no truncation, no unsupported answer and no missing score.

`historical_population_known` is always `false`. Deleted, expired and uncaptured answers cannot be reconstructed. The counts describe the answers that NoResponse still retains. They are not an estimate of the fraud that a change would prevent.

## Publish a change

Add `expected_policy_version` and `change_reason` to a `PATCH` of the settings:

```json title="PATCH /v1/workspaces/{workspace_id}/settings/live" theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "expected_policy_version": 7,
  "change_reason": "Review from 70 after the October preview; block from 90.",
  "flows": { "account_creation": { "review": 70, "block": 90 } }
}
```

**expected\_policy\_version** `integer`

Optional. The `policy_version` that you read. When it is no longer current, the change answers 409 [`policy_version_conflict`](https://docs.noresponse.org/problems/policy-version-conflict) and changes nothing. Read the settings again and review the newer version before you retry.

**change\_reason** `string`

Why you made the change, up to 500 characters without control characters. NoResponse keeps it with the version in the history.

Every successful change advances `policy_version`, including a change of `retention_days` alone.

## Read the history

`GET /v1/workspaces/{workspace_id}/settings/{environment}/history` lists the versions of the settings, newest first.

| Query parameter | What it does |
| - | - |
| `limit` | How many versions to answer, from 1 to 100. The default is 50. |
| `before_version` | Answers only versions older than this one. Pass `next_before_version` from the previous page. |

```json title="200 history" theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "items": [
    {
      "settings": { "workspace_id": "tnt_01JA2Z8K6Q4V7W9XBCDEFGHJKM", "environment": "live", "policy_version": 8, "...": "..." },
      "recorded_at": "2026-10-29T10:14:03Z",
      "change_reason": "Review from 70 after the October preview; block from 90."
    },
    {
      "settings": { "workspace_id": "tnt_01JA2Z8K6Q4V7W9XBCDEFGHJKM", "environment": "live", "policy_version": 7, "...": "..." },
      "recorded_at": "2026-09-02T08:00:00Z"
    }
  ],
  "has_more": true,
  "next_before_version": 7,
  "history_complete": false
}
```

**settings** `object`

The full settings of that version, in the same shape as `GET` of the settings.

**recorded\_at** `string`

When NoResponse captured the version.

**change\_reason** `string`

The reason sent with the change, when there was one.

**restored\_from** `integer`

The version that this version restored, when it is a restore.

**evaluation** `object`

When you published the version from a policy evaluation: the `evaluation_id`, the evaluator version, and the hashes of its manifest, input, cohort and result. It stays in the history after the evaluation expires.

**history\_complete** `boolean`

`false` when earlier versions predate the capture of history.

## Restore an earlier version

A restore publishes the settings of an earlier version as a new version. It never rewrites the history. Send all three fields:

```json title="POST /v1/workspaces/{workspace_id}/settings/live/restore" theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "version": 7,
  "expected_policy_version": 8,
  "change_reason": "Review queue doubled after version 8; back to version 7."
}
```

The restore sets `retention_days`, `flows` and `refused_sources` to the values of that version. It answers the settings in force, with a new `policy_version`. The history shows the new version with `restored_from: 7`.

* A stale `expected_policy_version` answers 409 [`policy_version_conflict`](https://docs.noresponse.org/problems/policy-version-conflict).
* A `version` that the history does not hold answers 404 [`not_found`](https://docs.noresponse.org/problems/not-found).

A restore does not revive assessments that a shorter retention already deleted. It does not undo what your platform already did with earlier decisions.

## Policy evaluations

A policy evaluation compares candidate thresholds for account creation against a snapshot of retained evidence and your labeled outcomes. It returns a report that you review. Only a completed `proposal_requires_review` result can be published, and only after you send its exact candidate and hashes.

> [!WARNING]
>
> Every evaluation returns `no_change`, because NoResponse does not attest that the diagnostic evidence it reads is complete. Publishing one answers 409 [`evaluation_no_proposal`](https://docs.noresponse.org/problems/evaluation-no-proposal). Use the report to study coverage and costs, and publish threshold changes with `PATCH` of the settings.

| Route | What it does |
| - | - |
| `POST /v1/workspaces/{workspace_id}/evaluations/{environment}` | Starts an evaluation. Answers 202 with the job. |
| `GET /v1/workspaces/{workspace_id}/evaluations/{environment}` | Lists the retained jobs. Pass `next_before` as `before` for the next page. `limit` is 1 to 100, 20 by default. |
| `GET /v1/workspaces/{workspace_id}/evaluations/{environment}/{evaluation_id}` | Reads the job's `status`, counts, expiry and hashes. |
| `GET /v1/workspaces/{workspace_id}/evaluations/{environment}/{evaluation_id}/result` | Reads the report of a completed job. |
| `POST /v1/workspaces/{workspace_id}/evaluations/{environment}/{evaluation_id}/cancel` | Stops the job and deletes its evidence and report. Send `{}`. |
| `POST /v1/workspaces/{workspace_id}/evaluations/{environment}/{evaluation_id}/publish` | Publishes a reviewed proposal as a new policy version. |

### Start an evaluation

Send an `Idempotency-Key` header with every request. A retry with the same key and body answers the same job. The same key with a different body answers 409 [`idempotency_key_reused`](https://docs.noresponse.org/problems/idempotency-key-reused).

```json title="POST /v1/workspaces/{workspace_id}/evaluations/live" theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "expected_policy_version": 8,
  "from": "2026-09-01T00:00:00Z",
  "to": "2026-10-01T00:00:00Z",
  "candidates": [
    { "step_up": 40, "review": 75, "block": null },
    { "step_up": 40, "review": 70, "block": 90 }
  ],
  "costs": {
    "legitimate": { "allow": 0, "step_up": 1, "review": 3, "block": 20 },
    "abuse": { "allow": 50, "step_up": 10, "review": 2, "block": 0 }
  },
  "limits": { "review_rate": 0.05, "server_error_rate": 0.001, "unavailable_result_rate": 0.01, "p95_ms": 2000 },
  "label_collection": "Chargebacks and manual review verdicts recorded as outcomes, September 2026.",
  "split_seed": "2026-10-eval-account-creation"
}
```

**expected\_policy\_version** `integer` *required*

The `policy_version` in force. The evaluation compares the candidates with the `account_creation` thresholds of this version. When the settings changed since you read them, the answer is 409 [`policy_version_conflict`](https://docs.noresponse.org/problems/policy-version-conflict).

**from, to** `string` *required*

The window of the account cohort, at most 31 days and in the past. The cohort is the accounts whose first `account_creation` assessment in the window NoResponse served and still retains.

**candidates** `object[]` *required*

From 1 to 200 sets of `step_up`, `review` and `block` (`null` for blocking off) to compare.

**costs** `object` *required*

The cost of each decision, `allow`, `step_up`, `review` and `block`, for a `legitimate` account and for an `abuse` account. These are your business assumptions, not measurements.

**limits** `object` *required*

The budgets that a candidate must stay within: `review_rate`, `server_error_rate` and `unavailable_result_rate` from 0 to 1, and `p95_ms` latency.

**label\_collection** `string` *required*

How you sampled and decided the labels, 20 to 2,000 characters.

**split\_seed** `string` *required*

A seed, 16 to 128 characters, that fixes which accounts the evaluation holds out. Choose it before you look at results.

The job evaluates a snapshot of retained evidence. It makes no new source requests and does not change your settings. The 202 answer has a `Retry-After` header. Wait that many seconds between each get of the job. Its `status` moves from `queued` and `running` to `completed`, `failed` or `cancelled`.

### Limits

* An environment retains at most five jobs. A sixth answers 409 [`evaluation_limit_reached`](https://docs.noresponse.org/problems/evaluation-limit-reached). A cancelled job counts until it expires.
* A job covers at most 1,000 accounts, 50,000 request attempts and 64 MiB of evidence. A larger cohort answers 413 [`evaluation_evidence_limit`](https://docs.noresponse.org/problems/evaluation-evidence-limit). Choose a smaller window.
* A job expires 24 hours after you create it, or earlier when retained evidence in it expires first. An expired job answers 404 [`not_found`](https://docs.noresponse.org/problems/not-found).
* When you delete source data of the cohort or shorten retention, NoResponse cancels the job. Its `error_code` names the reason.

### Read the report and publish

When `status` is `completed`, the result route answers the job and its `report`. The report has a `status` of `no_change` or `proposal_requires_review`, the chosen `candidate`, and the hashes of its input and cohort. Before completion, the result route answers 409 [`evaluation_conflict`](https://docs.noresponse.org/problems/evaluation-conflict).

To publish a `proposal_requires_review` result, send its exact candidate and hashes, with the baseline version and a reason:

```json title="POST /v1/workspaces/{workspace_id}/evaluations/live/pev_01JB0000000000000000000000/publish" theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "expected_policy_version": 8,
  "manifest_sha256": "<manifest_sha256 of the job>",
  "input_sha256": "<input_sha256 of the job>",
  "result_sha256": "<result_sha256 of the job>",
  "candidate": { "step_up": 40, "review": 70, "block": 90 },
  "change_reason": "Reviewed evaluation pev_01JB...: lower cost within the review budget."
}
```

A successful publish changes the `account_creation` thresholds, answers the new settings and records the evaluation in the history. These answer 409:

| Problem | When |
| - | - |
| [`evaluation_no_proposal`](https://docs.noresponse.org/problems/evaluation-no-proposal) | The job is not completed, its result is `no_change`, or you already published it. A cancelled job is not completed. |
| [`evaluation_conflict`](https://docs.noresponse.org/problems/evaluation-conflict) | `expected_policy_version` is not the baseline of the job, or the candidate or hashes do not match the report. |
| [`policy_version_conflict`](https://docs.noresponse.org/problems/policy-version-conflict) | The settings changed after the evaluation started. |

Cancelling an evaluation does not undo a version that you already published from it. To go back, [restore](#restore-an-earlier-version) the earlier version.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.