Independent risk framework
Risk subjects outlive verification sessions. Attach evidence to a person, business, account or transaction, evaluate a published policy, and retain the assessment and its explanation. A policy key identifies a purpose, such as onboarding or fraud; one subject can have separate current assessments for each.
This API is independent of Korala’s predefined session fraud evaluators. It does not automatically approve identities, change account permissions or deliver payment restrictions.
Authentication and environments
Use the usual HMAC API credentials or dashboard JWT. Every resource belongs
to an organization and a test/live environment. API keys are pinned to their
mode; a test key cannot read or mutate live resources. Dashboard JWTs default
to live; send X-Risk-Mode: test to manage test resources. OAuth assistant
connections cannot use these endpoints yet.
Owner/admin JWTs are required to create/publish policies, create/revoke overrides and cancel actions. Ordinary authenticated clients can record evidence, evaluate published policies and acknowledge action execution. These operations remain subject to organization and mode isolation.
Every mutation needs an Idempotency-Key header (1–120 letters, digits,
underscores, dots, colons or hyphens). Retry an identical operation with the
same key. Reusing a key with different input or a different principal returns
409. Successful retries return the same resource ID and its current data;
failed transactions do not consume the key.
Resources
All paths below are relative to /api/v1.
| Operation | Endpoint |
|---|---|
| Create a subject | POST /risk/subjects |
| Create a draft policy version | POST /risk/policies |
| Publish a policy | POST /risk/policies/{id}/publish |
| Append evidence | POST /risk/subjects/{id}/evidence |
| Evaluate a policy | POST /risk/subjects/{id}/assessments |
| Read currentness/effective decision | GET /risk/assessments/{id}/state |
| Record an override | POST /risk/assessments/{id}/overrides |
| Revoke an override | POST /risk/overrides/{id}/revoke |
| Update an action | POST /risk/actions/{id}/transitions |
| List records | GET /risk/{resource} |
| Read a record | GET /risk/{resource}/{id} |
Resources are subjects, policies, evidence, assessments, overrides,
actions and events. Responses wrap the record as { id, resource, data }.
List responses contain items and nextOffset; follow non-null offsets for
pages of at most 100 records and 1 MiB. A page can be shorter because of
its byte budget; always follow nextOffset, not the item count. Evidence, assessments, overrides, actions and
events support ?subjectId=.... Policies and subjects do not support that filter.
The OpenAPI schemas describe each record’s data. Assessment data includes the policy snapshot, evaluation time, sequence, evidence revision, previous assessment ID, exact input snapshots, score, band and factor/rule results.
Subjects and evidence
Create a subject with a kind and your externalId. The combination is unique
within the organization/environment. Use stable customer/account identifiers;
do not put names or raw identity documents in the external ID.
Evidence is a typed scalar or a list of scalars:
{
"fact": "monthly_usage_units",
"status": "available",
"value": 32,
"observedAt": "2026-09-27T09:00:00Z",
"expiresAt": "2026-10-27T09:00:00Z",
"providerRef": "report-123"
}Observation times cannot be in the future. For unavailable or revoked
evidence, value must be null. Only an available observation can have a
non-null value. Evidence values are limited to strings, finite numbers,
booleans and lists of those types; structured provider payloads belong in
your evidence storage, referenced by providerRef.
The source is assigned by the server as api_key:<database-key-id> or
user:<user-id>. Read it from the resulting evidence record and explicitly
accept that source in a policy. A submitted provider name does not establish
trust. Key rotation currently requires updating accepted sources in a new
policy version.
New evidence supersedes the latest receipt for the same subject/fact/source. The original record remains. A later receipt with an older observation time still supersedes the previous receipt, so clients must deliberately reconcile late events before submission. Revocation or unavailability never falls back to older favourable evidence. Accepted sources that disagree make an input unresolved. New evidence marks prior assessments stale.
Resource limits
Oversized operations return 413. Limits are measured as UTF-8 JSON bytes: 64 KiB for a normalized policy request, 16 KiB for an evidence request, 256 KiB across selected evidence rows (including stored metadata), and 512 KiB for the evaluation result. Full-record reads are capped at 768 KiB. Lists have a 1 MiB budget. These aggregate limits also apply when many small requests have accumulated evidence; a rejected assessment leaves no partial assessment/actions/audit event and does not consume its retry key.
Policies
Create a version with { key, definition }, inspect it, and publish it. Drafts
and published definitions are immutable through this API; creating another
version under the same key assigns the next version number. Assessments pin
an explicit published policyVersionId. Publishing a new version does not
silently reevaluate subjects or replace their historical policy.
The definition has these fields:
| Field | Meaning |
|---|---|
inputs | Required facts, types, accepted sources and maxAgeSeconds |
factors | Named factors with ordered condition/point cases, first/highest match, weight and optional default points |
aggregation | Sum or maximum of weighted factor contributions |
floor, ceiling | Bounds applied after aggregation |
bands | Ordered IDs and lower bounds, each with an effect |
rules | Conjunctive conditions with reasons, optional forced band and effects |
missingEvidence | awaiting_evidence or review |
reviewAfterSeconds | Optional freshness deadline for the assessment; not an automatic scheduled job |
Conditions support scalar equality (eq), membership (in), list membership
(contains), and numeric gt, gte, lt, lte. Types must agree with the
declared input. Rules contain all; use separate rules for alternative
conditions. Arbitrary scripts, expressions and network lookups are not allowed.
Factor match: first selects the first matching case. highest takes the
highest matched point value once, useful when a account declares multiple
access methods. Weights multiply factor points before aggregation. If no
case matches and there is no explicit defaultPoints, evaluation is incomplete.
Bands must start at zero and have strictly increasing lower bounds. They use inclusive lower/exclusive upper boundaries; the final band is unbounded. A score is not a probability. Do not compare scores across unrelated policies.
Effects contain a decision (none, review, restrict, reject) and an
array of named actions. Decision precedence is reject, restrict, review, none.
Forced bands only increase risk. Known mandatory rules still apply when other
inputs are missing. An incomplete assessment has a null numeric score.
none means this policy does not require a restriction; it is not KYC approval.
All policy limits and required properties are documented in the generated
CreateRiskPolicyDto schema. Nested group trees, maintained country-risk
tables and general arithmetic expressions are not supported in this version.
Assessments and current decisions
Call the assessment endpoint with { policyVersionId, trigger }. The service
evaluates persisted evidence and atomically stores its snapshot, explanation,
required actions and audit event. Replaying an assessment means using its
stored policy, inputs and evaluation time with the recorded evaluator version.
Historical GET always returns the original assessment. Before making a new
downstream decision, call its /state endpoint. It reports current: false
and effectiveDecision: reassessment_required if newer evidence arrived,
a newer assessment exists for the policy key, or a freshness/review deadline
passed. Refresh by explicitly creating a new assessment.
There is no automatic scheduler or outbound risk webhook in this release. Clients schedule reevaluations and poll action/state endpoints. Do not treat an old saved assessment or an action request as proof of current authorization.
Human decisions and action history
An owner/admin may override a current assessment with decision, a nonempty
reason and an expiresAt within one year. The calculated score/band stays
unchanged. Incomplete evidence cannot be overridden to none. New evidence
invalidates the currentness of the assessment and the applicability of its
override. Only the most recent override may apply; revoking or expiring it
does not reactivate earlier overrides.
Actions start as requested. Consumers acknowledge them, carry out their
own authorized operation, then report completion. Send status,
expectedRevision and reason for each transition. Stale revisions return 409.
requested → acknowledged → completed
│ │
└──→ failed ←─┘
│
└──→ requested (explicit retry)
requested / acknowledged / failed → cancelled (owner/admin only)Completed and cancelled actions are terminal. A new low-risk assessment does not automatically cancel earlier requirements. Each assessment can produce its own action: reconcile existing open actions for the subject before performing duplicate external work. Acknowledgements are client assertions, not independent proof that a payment system applied a restriction.
events records authenticated actors and reasons for publication, evidence
changes, assessments, overrides and action transitions in the same transaction
as the change. It is application-level history, not a cryptographically sealed
audit log. There are no record deletion endpoints or configurable retention
jobs yet; organization deletion cascades its risk data.
TypeScript client
const subject = await client.risk.createSubject(
{ kind: 'person', externalId: 'customer-123' },
'customer-123-create',
);
const assessment = await client.risk.assess(
subject.id,
{ policyVersionId: publishedPolicyId, trigger: 'onboarding' },
'customer-123-onboarding-1',
);
const state = await client.risk.state(assessment.id);Use client.risk.inMode('test') for owner/admin JWT sandbox governance and
reads. It returns a separate view and leaves client.risk in its default mode.
Selecting a contradictory mode with an API key returns 400; the credential
continues to determine its environment.
The methods also cover policy publication, evidence ingestion, overrides, action transitions, list and get. Mutation keys are explicit so retries can reuse them. Governance methods need an owner/admin JWT; normal API keys cannot publish a policy or grant themselves override privileges.
Korala check adapter and the next phase
POST /risk/subjects/{id}/session-checks accepts { checkId } for an existing
persisted session-level check. It verifies tenant and subject relationships,
derives the fact as check.<kind>, and retains the submitting principal as source.
The source is the submitting user:<id> or api_key:<id>, which the policy
must explicitly accept. A persisted check is not a trusted provider attestation:
workflow authors can configure its provider and check kind. The early
korala:session-checks source is retired: it is excluded from new assessments,
and existing assessments using it require reassessment. Historical results
remain available unchanged.
Provider failures become unavailable evidence. Review/block flags do not
become a clear input. Participant-level checks require a separate adapter and
are refused. Test mode is refused because sessions do not yet persist mode.
The adapter imports evidence from an existing session. Configure accepted sources and policy rules for your integration, then validate them in an isolated test environment. Keep proprietary policy definitions and assessment data out of public documentation and shared examples.