OpenKnowra Platform · Decision API

Logic Controller

Call a deployed decision policy from your own systems: the decision for any record, the rules that held, and the evidence behind each one.

POST /api/okp/d/{policyId}/decide
Base URL https://okp.openknowra.ai Auth Policy API key Content-Type application/json Also as MCP server · webhooks · OpenAPI

When to use this API

Use the Logic Controller when your own systems need the decision your business policy makes on a record: which rules hold for a customer, an asset, an order or a case, and why.

You build the policy in the Logic Controller on top of a context graph from the Context Compiler: your SOP is read into business rules you confirm, you test them on the graph's records, and you deploy a version. Every deployed version gets its own API. A call evaluates the deployed rules on the record you send, reading what you leave out from the graph and walking the record's relationships where a rule needs them. The answer names the version that made it, the rules that held, the rules another rule took precedence over, and the evidence for every clause.

The same policy is also served as an MCP server for AI agents, and it can push webhooks to your systems when a record's decision changes. To act on decisions (an agent, an assembly or a digital worker with human oversight), build on the policy in the Action Builder.

Versioned by design. The rules change only when someone deploys a new version in the console. Every answer carries version, so you can always tell which logic made a decision. A call always reads the version currently deployed.

Authentication

Every request carries the policy's API key. Create one in the console: open the decision policy in the Logic Controller and choose API & integration. A key opens this one policy only, is shown once when you create it (only a hash of it is kept), and can be set to expire after 30, 90 or 365 days. Keys start with ok_live_.

Either header
x-ok-api-key: ok_live_xxxxxxxxxxxxxxxxxxxxxxxx
Authorization: Bearer ok_live_xxxxxxxxxxxxxxxxxxxxxxxx
Rotating a key. Rotate creates a new key with the same label and lifetime; the old one keeps working for a grace period you choose (24 hours by default) so you can switch without downtime. A revoked key stops working at once.

Parameters

Path

NameTypeRequiredDescription
policyIdstringrequiredThe decision policy's id, shown under API & integration (for example uc-3f9a1c).

Body of POST /decide

NameTypeRequiredDescription
entitystringrequiredThe entity the record is, by its id or its label (for example Customer). GET / lists the entities the policy decides on.
recordobjectrequiredThe record's fields, its primary key included. When the key matches a record in the graph, any field you leave out is read from the graph, and relationship rules (for example "an open order over 100") are evaluated through the graph.

Request

curl -X POST https://okp.openknowra.ai/api/okp/d/$POLICY_ID/decide \
  -H "x-ok-api-key: $OPENKNOWRA_POLICY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"entity":"Customer","record":{"customerId":"C-1042","tenureMonths":4,"openComplaints":2}}'
import os, json, urllib.request

base = "https://okp.openknowra.ai/api/okp/d/" + os.environ["POLICY_ID"]
req = urllib.request.Request(
    base + "/decide",
    data=json.dumps({"entity": "Customer",
                     "record": {"customerId": "C-1042", "tenureMonths": 4, "openComplaints": 2}}).encode(),
    headers={"x-ok-api-key": os.environ["OPENKNOWRA_POLICY_KEY"], "Content-Type": "application/json"},
    method="POST")
print(json.load(urllib.request.urlopen(req, timeout=30)))
const base = `https://okp.openknowra.ai/api/okp/d/${process.env.POLICY_ID}`;

const res = await fetch(`${base}/decide`, {
  method: "POST",
  headers: { "x-ok-api-key": process.env.OPENKNOWRA_POLICY_KEY!, "Content-Type": "application/json" },
  body: JSON.stringify({ entity: "Customer", record: { customerId: "C-1042", tenureMonths: 4, openComplaints: 2 } }),
});
console.log(await res.json());
Ready-made clients. Under API & integration the console generates a TypeScript or Python client for your policy (built from its OpenAPI document), and a Node sample that both calls the policy and receives its webhooks.

Response

200 OK - the decision for the record under the deployed version.

application/json
{
  "ok": true,
  "version": 3,
  "entity": "Customer",
  "key": "C-1042",
  "known": true,
  "decision": [
    { "rule": "Early-tenure complaints", "ruleId": "r-7c21", "outcome": "Retention call", "kind": "rule" }
  ],
  "overridden": [
    { "rule": "Standard follow-up", "outcome": "Email follow-up", "by": "Early-tenure complaints", "evidence": "SOP 4.2" }
  ],
  "evidence": [
    {
      "rule": "Early-tenure complaints",
      "holds": true,
      "overridden": false,
      "clauses": [
        { "clause": "tenureMonths < 6", "holds": true, "observed": 4 },
        { "clause": "openComplaints >= 2", "holds": true, "observed": 2 }
      ]
    }
  ]
}
FieldTypeDescription
versionintegerThe deployed version that made the decision.
keystring | nullThe record's primary key, from the record you sent.
knownbooleanWhether the graph holds a record with this key (its other fields and relationships were then read from the graph).
decisionarrayThe rules that hold for the record after precedence, each with its outcome.
overriddenarrayRules that held but gave way to a rule that takes precedence (by), with the evidence for the precedence.
evidencearrayEvery rule that bears on the record, with each clause, whether it holds and the value observed. Fields marked sensitive in the policy are masked or shown as (protected).

All endpoints

Every path is relative to https://okp.openknowra.ai/api/okp/d/{policyId} and takes the same API key.

MethodPathWhat it does
GET/Describes the deployed version: its number and who deployed it, the entities it decides on (with their fields, and the allowed values where there are few), the rules per entity with the fields each reads, the webhook events, and every endpoint.
POST/decideThe decision for a record your system holds (above).
GET/decisionsThe decisions the deployed version holds for the graph's records, a page at a time. Query: entity, outcome (an outcome or a rule name), limit (1-500, default 50), offset. Returns { version, total, limit, offset, items: [{ entity, key, decision }] }.
GET/decisions/{entity}/{key}One record's decision with its evidence and the record itself (sensitive fields protected). 404 when the version holds no such record.
POST/eventsTells the policy a record changed in your system: the rules are evaluated again on the live state, the change is written to the decision ledger, and your webhooks fire (see below).
GET/openapi.jsonThe OpenAPI 3.1 document of this policy's API, for code generators and API gateways.

Record a change: POST /events

NameTypeRequiredDescription
entitystringrequiredThe entity of the record that changed.
keystringrequiredIts primary key. The graph must hold the record.
changesobjectrequiredThe fields that changed and their new values: { "openComplaints": 3 }. Numbers are read as numbers, true/yes/1 as true, and listed values case-insensitively. The primary key and reference fields are left as they are.
200 OK
{
  "ok": true,
  "version": 3,
  "id": "ev-9b1c2e",
  "at": "2026-10-14T09:12:44.120Z",
  "entity": "Customer",
  "key": "C-1042",
  "summary": "Customer C-1042 now holds Retention call. Recorded in the ledger as #12.",
  "decision": { "before": [], "after": [{ "rule": "Early-tenure complaints", "outcome": "Retention call" }] },
  "changed": [{ "entity": "Customer", "key": "C-1042", "gained": ["Retention call"], "lost": [] }],
  "webhooks": { "matched": 1, "sent": 1, "failed": 0, "pending": 0 }
}
Repeating a change is safe. Sending a change the record already has returns 400 no_changes and records nothing, so a retried call never writes a decision twice.

Webhooks

Add a webhook under API & integration: a URL, the events to send, and optionally a secret to sign them with. Narrow it to some outcomes or entities if you like.

EventSent when
rule_result.gainedA record starts holding a rule's outcome after a change (POST /events).
rule_result.lostA record stops holding a rule's outcome after a change.
version.publishedA new version is deployed, with what it changes: per rule, the records that gain or lose its outcome.

Headers

HeaderValue
x-ok-eventThe event type, as above.
x-ok-deliveryThe delivery's id (dlv-...). Use it to discard a delivery you have already handled.
x-ok-timestampWhen it was sent, in milliseconds since the epoch.
x-ok-agentThe policy's id.
x-ok-signaturesha256= followed by the hex HMAC-SHA256 of timestamp + "." + body with your secret. Sent only when the webhook has a secret.
Verify a delivery (Node)
import crypto from "node:crypto";

function verify(rawBody, headers, secret) {
  const ts = headers["x-ok-timestamp"];
  const want = "sha256=" + crypto.createHmac("sha256", secret).update(ts + "." + rawBody).digest("hex");
  const got = headers["x-ok-signature"] || "";
  const fresh = Math.abs(Date.now() - Number(ts)) < 5 * 60 * 1000;
  return fresh && got.length === want.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(want));
}
rule_result.gained
{
  "specVersion": "1.0",
  "id": "dlv-4f2a9c1e",
  "type": "rule_result.gained",
  "at": "2026-10-14T09:12:44.180Z",
  "useCase": { "id": "uc-3f9a1c", "title": "Retention policy" },
  "logic": { "version": 3, "publishedAt": "2026-10-10T08:00:00.000Z" },
  "rule": { "id": "r-7c21", "label": "Early-tenure complaints", "outcome": "Retention call", "entity": "Customer" },
  "record": { "entity": "Customer", "key": "C-1042", "values": { "customerId": "C-1042", "tenureMonths": 4, "openComplaints": 3 } },
  "cause": { "eventId": "ev-9b1c2e", "changes": { "openComplaints": { "before": 2, "after": 3 } } },
  "evidence": [{ "clause": "openComplaints >= 2", "observed": 3 }]
}
Delivery. Each delivery is sent once, with an 8-second timeout. Answer with a 2xx status to acknowledge it. Deliveries and their results are listed under API & integration, where you can also send a test delivery.

MCP server

The same policy is an MCP server (Streamable HTTP, JSON-RPC 2.0), so an AI agent can ask it for decisions. It takes the same API key.

MCP client configuration
{
  "mcpServers": {
    "retention-policy": {
      "type": "http",
      "url": "https://okp.openknowra.ai/api/okp/mcp/<policyId>",
      "headers": { "Authorization": "Bearer ok_live_xxxxxxxxxxxxxxxxxxxxxxxx" }
    }
  }
}
ToolWhat it does
describe_decisionsThe entities, fields and rules of the deployed version.
decideThe decision for a record, as POST /decide.
get_decisionOne record's decision with its evidence.
list_decisionsThe decisions the version holds, filtered by entity or outcome.

Pricing

Decisions are not metered: /decide, /decisions, /events, webhooks and the MCP server carry no per-call charge. A policy reads from a context graph, which is billed under the Context Compiler. For enterprise terms, see the Logic Controller pricing.

Errors

Every error is { "error": "<code>", "message": "<what to do>" }.

StatusCodeMeaning
400unknown_entityThe entity is not one the policy decides on. The message lists the ones it does.
400record_required/decide was sent without a record.
400record_not_found · bad_value · value_not_allowed · no_changes/events: the graph holds no such record, a value is not of the field's type or not one of its allowed values, or nothing changed.
401invalid_api_keyMissing, revoked or expired key, or a key for another policy.
404not_foundNo such policy, or the version holds no such record.
409not_publishedThe policy has no deployed version yet. Deploy one in the console.
413-The body is over 2 MB.
423pausedThe policy is paused in the console. Calls resume when it is resumed.
5xxfailedTransient. Retry with exponential backoff.