Skip to main content

Enforce rules with OPA

OPA (Open Policy Agent) lets you write rules that every returned answer must satisfy. Its rule language is Rego. Use it when acceptable output depends on relationships between values, application facts or permissions—not just a field's type or format.

Examples include a total equalling its line items, an end date following its start, or an action requiring a supplied OAuth scope. JSON Schema defines the output; Rego checks whether a candidate answer meets the additional requirements.

What a policy guarantees​

Struct checks answers against your policy. If an answer fails, it searches for alternatives within the configured budget. It returns an answer only when all policy requirements are satisfied.

Think of this as searching for a likely answer inside the allowed set. It is guided by the model's predictions, not an exhaustive search or a guarantee of the globally most probable answer.

Struct is a system-one model. It can search more effectively when it understands the task and has a useful idea of what a solution looks like. A needle-in-a-haystack puzzle, an impossible requirement or a task beyond its knowledge may exhaust the budget. In that case it returns 422 POLICY_SEARCH_EXHAUSTED, without a noncompliant output.

Choose between a description, schema and policy​

NeedUse
Explain what a field means or how to interpret evidencedescription
Restrict types, labels, formats or supported combinations of output fieldsJSON Schema
Make finite choices eligible based on supplied factsState in schema conditions
Enforce a calculation or a richer relationship with input, state or permissionsOPA

Clear descriptions help the model find valid answers. Put the task's meaning and relevant rules in them, as well as enforcing mandatory rules in Rego. More retries alone cannot supply missing knowledge or resolve contradictory requirements.

Example: keep an extracted total consistent​

The input is a short receipt. The model extracts the amounts into integer cents; the policy requires the reported total to equal the sum. A wrong interpretation could still pass the arithmetic check, so this rule guarantees consistency, not perfect extraction.

Save the schema and policy files:

checked-sum.schema.json
{
"type": "object",
"properties": {
"amounts_cents": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"description": "Extract each receipt amount in order as integer cents. EUR 12.50 means 1250. Do not include a separate total as a line item.",
"items": {
"type": "integer"
}
},
"total_cents": {
"type": "integer",
"description": "The sum of the extracted amounts, in integer cents."
}
},
"required": [
"amounts_cents",
"total_cents"
],
"additionalProperties": false
}
package totals

default allow := false

allow if {
every amount in input.output.amounts_cents { amount > 0 }
input.output.total_cents == sum(input.output.amounts_cents)
}

default allow := false makes the policy deny by default. The allow rule permits an answer when all its expressions hold. input.output is the candidate answer; sum adds the amounts. Policies can also read the original request through input.input, input.state, input.parameters and input.user_context. Optional context is absent when it is omitted or null.

Send the policy with the request​

policy.modules contains the Rego source loaded from the file. entrypoint: "data.totals.allow" names the rule to evaluate; it must return true for a successful answer.

Before you run this example

Call https://api.blatent.ai from your server. Set your API key in the same terminal where you run the example:

export BLATENT_API_KEY='YOUR_SERVICE_KEY'

Python requires Python 3.10+ and HTTPX:

python -m pip install "httpx>=0.28,<1"

TypeScript requires Node.js 24+ and uses its built-in fetch. The cURL examples require curl and jq.

Save the displayed files and the selected client in one directory. Run the client there with python example.py, node example.ts or bash example.sh. Downloads use the example's name in place of example.

import json
import os
from pathlib import Path
import httpx

request = {
"input": "Extract the amounts from this receipt: train ticket EUR 12.50; lunch EUR 7.50. Return the line amounts and their total in cents.",
"policy": {
"engine": "opa",
"entrypoint": "data.totals.allow",
"modules": {
"checked-sum.rego": Path("checked-sum.rego").read_text(encoding="utf-8"),
},
},
"policy_max_retries": 8,
"schema": json.loads(Path("checked-sum.schema.json").read_text(encoding="utf-8")),
}
with httpx.Client(
base_url="https://api.blatent.ai",
headers={"Authorization": f"Bearer {os.environ['BLATENT_API_KEY']}"},
timeout=195,
) as client:
response = client.post("/v1/resolve", json=request)
response.raise_for_status()
print(json.dumps(response.json()["output"]))

Illustrative output: {"amounts_cents":[1250,750],"total_cents":2000}. The same amounts with a total of 1999 are denied.

policy_max_retries: 8 permits up to eight retries after the initial attempt. A retry happens inside this API request; the client does not resubmit each candidate. Search budgets and feedback explains budgets and failure handling.

What Rego can read​

Rego pathSource
input.outputThe candidate JSON answer
input.inputThe request's input
input.stateApplication context attached to the request
dataRego rules and any immutable data supplied in the policy definition

For example, permission claims attached at state.oauth.scopes are available at input.state.oauth.scopes. The oauth name is an application-chosen key, not another API authentication mechanism. State in OPA rules and permission context have complete examples.

The policy source also guides the model. Private policy data is not automatically added to its prompt. If no policy is supplied, there is no policy guidance or policy recovery session.

Reuse and extend​

You can send the policy inline as above or register a policy version and use policy_id. This choice is independent of whether the schema is inline or registered.

See date relationships, exact text counts and permissions for further examples. The policy reference describes supported built-ins and limits.