Skip to main content

Policies

A Policy is a deterministic rule the Policy Engine evaluates on every trace ingestion. The engine returns the highest-priority action across all matching policies and translates it into an HTTP status: 403 for block, 202 for flag-for-review, 201 for approved. Policies are workspace-scoped, soft-deletable, and auditable through the Operations Audit Log. Tested against API version v1. JWT auth on every endpoint; mutations require the admin or owner role.

The Policy object

FieldTypeRequiredDescription
_id / idstringyesMongoDB ObjectId (also exposed as id virtual)
namestringyesOperator-readable name; max 100 characters
descriptionstringnoFree-form context; max 500 characters
enabledbooleannoDefault true; disabled policies are not evaluated
prioritynumbernoDefault 1; lower numbers evaluate first
conditionsCondition[]yesAll conditions must match (with AND/OR composition)
actionsAction[]yesOne or more actions to apply when conditions match
organizationIdstringyesOwning org
workspaceIdstringyesWorkspace scope
matchCountnumbernoRead-only; incremented on each match
lastMatchedstring (ISO 8601)noRead-only; last match timestamp
createdAt / updatedAtstring (ISO 8601)noStandard timestamps

Condition

FieldTypeRequiredDescription
fieldstringyesTrace field path (e.g., confidenceScore, outputDecision.action, tags)
operatorenumyesOne of equals, contains, greater_than, less_than, regex
valueanyyesComparison value; type matches the field
logicalOperatorenumnoAND or OR — how this condition combines with the next

Action

FieldTypeRequiredDescription
typeenumyesOne of block, flag_for_review, notify, approve
configobjectnoType-specific configuration (e.g., notify channel)

Engine semantics

Every trace evaluates against every enabled policy in the workspace in priority order. Conditions on a policy combine via the per-condition logicalOperator (default AND). On match, the Policy Engine collects every matched action across every matched policy and resolves the winning verdict:

priority: block > flag_for_review > notify > approve

A trace with one matched block and three matched flag_for_review returns block. A trace with no matches returns approve (HTTP 201). The matched-policy name and reason ride along on the response so the agent can log the gate.

Endpoints

GET    /api/v1/policies/templates
POST /api/v1/policies/from-template
GET /api/v1/policies
POST /api/v1/policies
PATCH /api/v1/policies/:id
DELETE /api/v1/policies/:id

List policies

GET /api/v1/policies

Returns every non-soft-deleted policy in the caller's organisation. Workspace-scoped via x-workspace-id.

curl
curl https://api.adjudon.com/api/v1/policies \
-H "Authorization: Bearer $ADJUDON_API_KEY"

Errors: 401, 500 INTERNAL_ERROR.

Create a policy

POST /api/v1/policies
Body fieldRequiredDescription
nameyesNon-empty string
conditionsyesArray of Condition objects
actionsyesArray of Action objects
descriptionnoFree-form context
prioritynoDefaults to 1
enablednoDefaults to true
workspaceIdnoDefaults to caller's active workspace
curl — block low-confidence loan denials
curl -X POST https://api.adjudon.com/api/v1/policies \
-H "Authorization: Bearer $ADJUDON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Block low-confidence loan denials",
"priority": 10,
"conditions": [
{ "field": "confidenceScore", "operator": "less_than", "value": 0.7, "logicalOperator": "AND" },
{ "field": "outputDecision.action", "operator": "equals", "value": "deny" }
],
"actions": [
{ "type": "block" }
]
}'
Python
import os, requests
r = requests.post(
"https://api.adjudon.com/api/v1/policies",
headers={"Authorization": f"Bearer {os.environ['ADJUDON_API_KEY']}"},
json={
"name": "Block low-confidence loan denials",
"priority": 10,
"conditions": [
{"field": "confidenceScore", "operator": "less_than", "value": 0.7, "logicalOperator": "AND"},
{"field": "outputDecision.action", "operator": "equals", "value": "deny"},
],
"actions": [{"type": "block"}],
},
)

Errors: 400 VALIDATION_ERROR (missing name, non-array conditions/actions), 401, 403 (role gate), 500.

Update a policy

PATCH /api/v1/policies/:id

Partial update; any subset of name, description, enabled, priority, conditions, actions may be supplied.

curl — disable a policy
curl -X PATCH https://api.adjudon.com/api/v1/policies/65b1f2c4 \
-H "Authorization: Bearer $ADJUDON_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "enabled": false }'

Errors: 401, 404 NOT_FOUND, 500.

Delete a policy

DELETE /api/v1/policies/:id

Soft-delete via the softDelete plugin: the row stays in the database, marked deleted; subsequent GET responses exclude it; the operations audit-log entry on policy.delete remains by Cardinal Rule 5.

Errors: 401, 404 NOT_FOUND, 500.

Templates

GET   /api/v1/policies/templates
POST /api/v1/policies/from-template

GET /templates returns the library of pre-built policy shapes a new organisation can import without writing conditions by hand. POST /from-template clones the template into a real Policy attached to the caller's workspace, optionally overriding the name. Errors: 400 VALIDATION_ERROR (missing templateId), 404 NOT_FOUND (template id not in library), 500.

Common gotchas

  • Idempotency. POST /policies and POST /from-template are mutating endpoints but the Idempotency-Key middleware is currently wired only on POST /traces. A retried policy create produces a duplicate. Send a unique name per attempt and check the audit log if you suspect a duplicate.
  • workspaceId is required on the schema. Omit it on the body and the server falls back to the caller's active workspace; pass it explicitly when scripting cross-workspace creates.
  • Priority is ASCending. A policy with priority: 1 evaluates before priority: 100. The verdict order (`block > flag > notify

    approve) is independent of priority` — priority only controls evaluation order, not which action wins.

  • Soft-delete. A deleted policy is invisible to GET but still in the database. There is no restore endpoint today; recreate.

See also

  • Policies & Human Review — the concept page covering the engine semantics in narrative form
  • Traces API — the endpoint where policy verdicts are returned (201 / 202 / 403)
  • Reviews API — what flag_for_review lands on
  • Error Codes — the failure envelope including ADJ_BLOCKED_BY_POLICY