Skip to Content
Extraction and verificationIndependent risk framework

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.

OperationEndpoint
Create a subjectPOST /risk/subjects
Create a draft policy versionPOST /risk/policies
Publish a policyPOST /risk/policies/{id}/publish
Append evidencePOST /risk/subjects/{id}/evidence
Evaluate a policyPOST /risk/subjects/{id}/assessments
Read currentness/effective decisionGET /risk/assessments/{id}/state
Record an overridePOST /risk/assessments/{id}/overrides
Revoke an overridePOST /risk/overrides/{id}/revoke
Update an actionPOST /risk/actions/{id}/transitions
List recordsGET /risk/{resource}
Read a recordGET /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:

FieldMeaning
inputsRequired facts, types, accepted sources and maxAgeSeconds
factorsNamed factors with ordered condition/point cases, first/highest match, weight and optional default points
aggregationSum or maximum of weighted factor contributions
floor, ceilingBounds applied after aggregation
bandsOrdered IDs and lower bounds, each with an effect
rulesConjunctive conditions with reasons, optional forced band and effects
missingEvidenceawaiting_evidence or review
reviewAfterSecondsOptional 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.

Last updated on