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:
| Shape | Meaning |
|---|---|
{"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:
| Operator | Meaning | Example |
|---|---|---|
eq | Exact equality | {"field": "provider", "op": "eq", "value": "openai"} |
neq | Not equal | {"field": "provider", "op": "neq", "value": "xai"} |
in | Value is in list | {"field": "model", "op": "in", "value": ["gpt-4o", "gpt-4o-mini"]} |
not_in | Value is not in list | {"field": "model", "op": "not_in", "value": ["gpt-4o-mini"]} |
gt | Greater than | {"field": "estimated_cost", "op": "gt", "value": 0.05} |
gte | Greater than or equal | {"field": "token_estimate", "op": "gte", "value": 1000} |
lt | Less than | {"field": "context._keel.request_hour_utc", "op": "lt", "value": 9} |
lte | Less than or equal | {"field": "context._keel.request_hour_utc", "op": "lte", "value": 17} |
contains | String contains substring, or collection contains element | {"field": "attrs.operation", "op": "contains", "value": "image"} |
exists | Field path resolves or does not resolve | {"field": "context.account_tier", "op": "exists", "value": true} |
starts_with | String prefix match | {"field": "model", "op": "starts_with", "value": "gpt-"} |
ends_with | String suffix match | {"field": "model", "op": "ends_with", "value": "-mini"} |
matches_regex | Safe-subset regex search | {"field": "context.email", "op": "matches_regex", "value": "@acme\\.com$"} |
len_gt | Length greater than | {"field": "attrs.allowed_regions", "op": "len_gt", "value": 3} |
len_gte | Length greater than or equal | {"field": "attrs.allowed_regions", "op": "len_gte", "value": 1} |
len_lt | Length less than | {"field": "attrs.allowed_regions", "op": "len_lt", "value": 10} |
len_lte | Length 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.
| Plan | Authoring level | Operators allowed |
|---|---|---|
| Starter | Templates only | None directly. Apply preset templates via POST /v1/projects/{project_id}/apply-policy-template/{template_id}. |
Growth (entitlement: policy_basic_authoring) | Basic | eq, neq, in, not_in, gt, gte, lt, lte, contains |
Business and Enterprise (entitlement: policy_full_authoring) | Full | All 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:
| Path | Type | Meaning |
|---|---|---|
model | string | Resolved request model |
provider | string | Resolved request provider |
estimated_cost | number | Estimated request cost in USD when pricing is available |
token_estimate | integer | Estimated input + output tokens |
project_id | UUID | Project identifier |
org_id | UUID or null | Organization identifier when present |
Nested fields
| Path | Source | Meaning |
|---|---|---|
attrs.* | Request resource attributes | Free-form attributes from resource.attributes in the permit request |
context.* | Caller-supplied context | Free-form context from the context field in the permit request |
context._keel.request_time_utc | Platform enrichment | ISO 8601 UTC timestamp when available |
context._keel.request_hour_utc | Platform enrichment | Request hour in UTC (0–23) when available |
context._keel.request_day_of_week | Platform enrichment | Day of week (0 = Monday) when available |
context._keel.project_plan | Platform enrichment | Canonical plan tier when available |
context._keel.ip_address | Platform enrichment | Client IP when available |
context._keel.country | Platform enrichment | Country code when available |
Important limits:
- There is no dedicated
resource.*namespace. Resource attributes must appear underattrs.*orcontext.*. - 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_keelfields 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.modelcontext.providercontext.provider_meta.regioncontext.provider_meta.data_retentioncontext.estimated_cost_usd_microscontext.prompt_token_countcontext.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"}
]
}
]
}