Skip to Content
PoliciesPolicy Conditions

Policy Conditions Reference

Every policy rule has an "if" node that determines when the rule fires. This page documents the full condition syntax, operator catalog, and field vocabulary available in policy rules.

For rule actions and complete policy examples, see Policy Reference.

Condition structure

Four shapes are supported:

ShapeMeaning
{"all": [...]}AND — all child conditions must be true
{"any": [...]}OR — at least one child condition must be true
{"not": {...}}Negation of a single condition
{"field": "...", "op": "...", "value": ...}Leaf comparison

Nodes can be nested arbitrarily deep.

  • {"all": []} is always true
  • {"any": []} is always false

Operators

Keel supports 17 operators:

OperatorMeaningExample
eqExact equality{"field": "provider", "op": "eq", "value": "openai"}
neqNot equal{"field": "provider", "op": "neq", "value": "xai"}
inValue is in list{"field": "model", "op": "in", "value": ["gpt-4o", "gpt-4o-mini"]}
not_inValue is not in list{"field": "model", "op": "not_in", "value": ["gpt-4o-mini"]}
gtGreater than{"field": "estimated_cost", "op": "gt", "value": 0.05}
gteGreater than or equal{"field": "token_estimate", "op": "gte", "value": 1000}
ltLess than{"field": "context._keel.request_hour_utc", "op": "lt", "value": 9}
lteLess than or equal{"field": "context._keel.request_hour_utc", "op": "lte", "value": 17}
containsString contains substring, or collection contains element{"field": "attrs.operation", "op": "contains", "value": "image"}
existsField path resolves or does not resolve{"field": "context.account_tier", "op": "exists", "value": true}
starts_withString prefix match{"field": "model", "op": "starts_with", "value": "gpt-"}
ends_withString suffix match{"field": "model", "op": "ends_with", "value": "-mini"}
matches_regexSafe-subset regex search{"field": "context.email", "op": "matches_regex", "value": "@acme\\.com$"}
len_gtLength greater than{"field": "attrs.allowed_regions", "op": "len_gt", "value": 3}
len_gteLength greater than or equal{"field": "attrs.allowed_regions", "op": "len_gte", "value": 1}
len_ltLength less than{"field": "attrs.allowed_regions", "op": "len_lt", "value": 10}
len_lteLength less than or equal{"field": "attrs.allowed_regions", "op": "len_lte", "value": 5}

Plan tier availability

Custom policy authoring is gated by plan tier. The runtime engine evaluates all 17 operators on every project — the gate is on what callers may author in custom policies, not on what the engine supports.

PlanAuthoring levelOperators allowed
StarterTemplates onlyNone directly. Apply preset templates via POST /v1/projects/{project_id}/apply-policy-template/{template_id}.
Growth (entitlement: policy_basic_authoring)Basiceq, neq, in, not_in, gt, gte, lt, lte, contains
Business and Enterprise (entitlement: policy_full_authoring)FullAll 17 operators, including exists, starts_with, ends_with, matches_regex, and the four length operators

Authoring violations are rejected at write time with the policy_authoring_level_exceeded error code. The error envelope identifies the offending rule index and the upgrade target.

Field vocabulary

Top-level fields

These fields are available in every policy evaluation:

PathTypeMeaning
modelstringResolved request model
providerstringResolved request provider
estimated_costnumberEstimated request cost in USD when pricing is available
token_estimateintegerEstimated input + output tokens
project_idUUIDProject identifier
org_idUUID or nullOrganization identifier when present

Nested fields

PathSourceMeaning
attrs.*Request resource attributesFree-form attributes from resource.attributes in the permit request
context.*Caller-supplied contextFree-form context from the context field in the permit request
context._keel.request_time_utcPlatform enrichmentISO 8601 UTC timestamp when available
context._keel.request_hour_utcPlatform enrichmentRequest hour in UTC (0–23) when available
context._keel.request_day_of_weekPlatform enrichmentDay of week (0 = Monday) when available
context._keel.project_planPlatform enrichmentCanonical plan tier when available
context._keel.ip_addressPlatform enrichmentClient IP when available
context._keel.countryPlatform enrichmentCountry code when available

Important limits:

  • There is no dedicated resource.* namespace. Resource attributes must appear under attrs.* or context.*.
  • There is no dedicated routing.* namespace.
  • There is no array indexing syntax in field paths.
  • context._keel.* fields are enriched by Keel opportunistically before evaluation. Caller-supplied _keel.* values are preserved and not overwritten. Each enrichment field fails independently — a missing value does not abort permit evaluation. Write policies using _keel fields with the expectation that a field may be absent.

Field availability by plan tier

Growth-plan custom policies (entitlement: policy_basic_authoring) may reference only a fixed whitelist of seven fields:

  • context.model
  • context.provider
  • context.provider_meta.region
  • context.provider_meta.data_retention
  • context.estimated_cost_usd_micros
  • context.prompt_token_count
  • context.time_of_day

Business and Enterprise plans (entitlement: policy_full_authoring) may reference any field documented above, including all attrs.*, context.*, and context._keel.* paths. Field-vocabulary violations on the Growth grammar are rejected at write time with policy_authoring_level_exceeded.

Cross-field comparison

Leaf nodes can compare one field against another field by using a {"field": "..."} object as the value:

{ "field": "token_estimate", "op": "gt", "value": {"field": "attrs.max_output_tokens_requested"} }

If the referenced field does not resolve, the comparison evaluates to false.

Operator semantics

exists

exists checks whether the path resolves, not whether the resolved value is truthy.

  • "value": true — the field must exist
  • "value": false — the field must not exist

Non-boolean value literals fail closed.

Numeric operators (gt, gte, lt, lte)

Both operands must be numeric (int or float, not bool). Non-numeric operands fail closed.

contains

  • On strings: substring containment
  • On lists, tuples, or sets: element containment
  • On other types: evaluates to false

starts_with / ends_with

Both operands must be strings. Comparison is case-sensitive.

matches_regex

matches_regex requires Business or higher (entitlement: policy_full_authoring).

Regex support is intentionally narrow to prevent denial-of-service risk in the policy evaluation path.

Write-time validation rejects:

  • patterns longer than 500 characters
  • backreferences
  • lookaround assertions
  • patterns that can trigger catastrophic backtracking

Runtime matching is capped at 5 ms and fails closed on timeout or error. A single policy document may contain at most 10 matches_regex conditions.

Prefer starts_with, ends_with, or contains when they are sufficient.

Length operators (len_gt, len_gte, len_lt, len_lte)

Applies to: strings, lists, tuples, sets, and dicts (by key count).

Missing fields, unsupported types, and negative or non-integer thresholds fail closed.

Resolution semantics

  • Field paths are dot-path traversal over nested mappings
  • Empty paths do not match
  • Paths containing ".." do not match
  • Missing intermediate keys do not match
  • Missing fields fail closed for all comparison operators except exists

Examples

Deny outside business hours

{ "any": [ {"field": "context._keel.request_hour_utc", "op": "lt", "value": 9}, {"field": "context._keel.request_hour_utc", "op": "gte", "value": 17} ] }

Restrict by plan tier

{"field": "context._keel.project_plan", "op": "eq", "value": "starter"}

Match on request attributes

{ "all": [ {"field": "attrs.operation", "op": "eq", "value": "generate.text"}, {"field": "model", "op": "ends_with", "value": "-mini"} ] }

Check optional field presence before comparing

{ "all": [ {"field": "context.account_tier", "op": "exists", "value": true}, {"field": "context.account_tier", "op": "eq", "value": "free"} ] }

Compare one field to another

{ "field": "token_estimate", "op": "gt", "value": {"field": "attrs.max_output_tokens_requested"} }

Nested AND / OR

{ "all": [ {"field": "provider", "op": "eq", "value": "openai"}, { "any": [ {"field": "context._keel.country", "op": "eq", "value": "US"}, {"field": "context._keel.country", "op": "eq", "value": "CA"} ] } ] }
Last updated on Edit this page on GitHub