# risk-policy-authoring

Source: https://docs.korala.ai/guides/risk-policy-authoring

---

# Author risk policies with JSON

The visual editor and API use the same JSON policy definition. An agent can
prepare a definition, validate it, test synthetic scenarios, and hand the draft
to a reviewer in the visual editor.

## Discover the contract

`GET /api/v1/risk/policy-schema` returns `schemaVersion`, `schema` and a short
explanation. The `schema` property is a JSON Schema document suitable for editor
validation; its identifier is `urn:korala:risk-policy:1`.

Set `schemaVersion: "1"` in new definitions. This is the **format version**, not
the revision of a customer's policy. Older definitions without this property
are interpreted as format version 1. Unsupported versions are rejected.

JSON Schema describes the shape, allowed operators and bounds. The server also
checks relationships between fields, such as matching input types, increasing
band thresholds and references to real workspace verification fields. Always
call the validation endpoint before treating generated JSON as valid.

`GET /api/v1/risk/verification-inputs` lists the current workspace's configured
Korala fields in the credential's environment. The response contains `items` and
`nextOffset`. Pass a non-null `nextOffset` as `?offset=...` to load the next page,
even when `items` is empty: a page can contain workflows with no supported fields.
Use the returned workflow ID,
version timestamp, step index, step type and field in a native input binding.
Copy its `source` into `acceptedSources`; use `ageSource` when you set the
`age_years` transform. These source IDs bind the exact workflow version, field
and transform.
Do not invent workflow IDs or use another workspace's bindings. External input
keys are defined by your integration; its authenticated API key is the source.

## Validate without saving

```http
POST /api/v1/risk/policies/validate
Content-Type: application/json

{ "definition": { "schemaVersion": "1", "inputs": [], "factors": [], "bands": [] } }
```

The deliberately incomplete definition above returns `valid: false` and an
`errors` array. Each error has a `path` and `message`. Structural errors identify
a JSON Pointer; relationship checks may identify the whole definition or its
inputs. Valid responses include the normalized `definition`, including defaults.

Validation creates no policy version. It requires authentication but no
idempotency key. Native bindings are checked against the authenticated workspace
and test/live environment. A changed workflow requires selecting its field again.

## Simulate synthetic scenarios

`POST /api/v1/risk/policies/simulate` accepts the definition and up to 20 scenarios:

```typescript
// `definition` is the policy JSON you want to test.
const validation = await client.risk.validatePolicy({ definition });
if (!validation.valid || !validation.definition) {
  throw new Error(JSON.stringify(validation.errors));
}
const simulation = await client.risk.simulatePolicy({
  definition: validation.definition,
  scenarios: [{
    name: 'Missing evidence',
    at: '2026-01-01T12:00:00Z',
    observations: [],
  }],
});
```

Each scenario provides a fixed evaluation time and at most 200 observations.
An observation contains `fact`, `source`, `status`, `value`, and `observedAt`;
`receivedAt` and `expiresAt` are optional. Accepted statuses are `available`,
`unavailable` and `revoked`. A boolean false is evidence; absent evidence is not.
Supply only the latest receipt for each fact/source pair, as selected evidence
would be supplied to a saved assessment. Duplicate pairs are rejected; use
separate scenarios to simulate successive receipts or revocation.

The response has `simulation: true`. Each scenario includes applicability and
an evaluation explaining input selection, missing evidence, factor contributions,
matched rules, band, decision and requested action keys. Evaluate applicability
alongside the result: a policy that does not apply must not authorize an action.

Simulation uses the same evaluator as saved assessments, but only on the supplied
synthetic observations. It does not read a customer's evidence, invoke verification
providers, save assessments, create action requests, or send webhooks. Source
strings in a simulation are hypothetical, not authenticated proof. These results
must never be used to authorize customer activity.

## Review and publish

Use **Edit policy JSON** in the policy editor to paste or copy a definition.
**Validate and apply** updates the visual editor without saving or publishing.
The setup order is Inputs, Risk bands, Scoring, Rules, Freshness, Review.

`POST /api/v1/risk/policies` saves a draft; publication remains a separate request
to `POST /api/v1/risk/policies/{id}/publish`. Both require an owner/admin dashboard
JWT and an idempotency key. API keys can discover, validate and simulate policies;
they cannot create or publish policies. These endpoints do not add OAuth/MCP
permissions: assistant access remains governed by the assistant scope policy.

The TypeScript client exposes `risk.policySchema()`, `risk.verificationInputs()`,
`risk.validatePolicy({ definition })` and
`risk.simulatePolicy({ definition, scenarios })`. Use the existing draft and
publication methods for governance. Test credentials remain confined to test mode.
