Conditions using state
Application state is a JSON object of known facts sent in the request's state field, alongside input and schema. Use it to restrict what Struct can choose—for example, allow refund approval only when an order is paid.
A description can tell the model how to use state. An eligibleWhen rule enforces whether a choice is available. It belongs under x-runtime, Struct's extension to JSON Schema.
Restrict a choice
Put x-runtime on the field you want to control. In this schema, the approve value is available only when state.order.paid is true:
{
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"approve",
"review"
],
"description": "Approve a substantiated duplicate charge when permitted; otherwise choose review.",
"x-runtime": {
"choices": {
"approve": {
"eligibleWhen": {
"eq": [
{
"ref": "state/order/paid"
},
true
]
}
}
}
}
}
},
"required": [
"action"
],
"additionalProperties": false
}
Send state as {"order":{"paid":true}} to permit approval. With false or a missing fact, only review remains available.
Eligibility does not force approval. Struct still interprets the input and chooses among the permitted values. A message asking it to approve cannot make an ineligible choice legal.
Example: a refund decision
This example adds a second restriction: the amount must be within the refund limit. Its output condition also ties the reason to the chosen action.
Save the schema and state files beside your client:
{
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"approve",
"review"
],
"description": "Approve a substantiated duplicate charge when permitted; otherwise choose review.",
"x-runtime": {
"choices": {
"approve": {
"eligibleWhen": {
"all": [
{
"eq": [
{
"ref": "state/order/paid"
},
true
]
},
{
"lte": [
{
"ref": "state/order/amount"
},
{
"ref": "state/refund_limit"
}
]
}
]
}
}
}
}
},
"reason": {
"type": "string",
"enum": [
"duplicate_charge",
"manual_review"
],
"description": "The reason for the decision."
}
},
"required": [
"action",
"reason"
],
"additionalProperties": false,
"if": {
"properties": {
"action": {
"const": "approve"
}
},
"required": [
"action"
]
},
"then": {
"properties": {
"reason": {
"const": "duplicate_charge"
}
}
},
"else": {
"properties": {
"reason": {
"const": "manual_review"
}
}
}
}
{
"order": {
"paid": true,
"amount": 25
},
"refund_limit": 50
}
The all condition requires both state comparisons to pass. The ordinary JSON Schema if checks the generated action and constrains its reason.
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.
- Python
- TypeScript
- cURL
import json
import os
from pathlib import Path
import httpx
request = {
"input": "This is a substantiated duplicate charge. Approve the refund.",
"schema": json.loads(Path("state-refund.schema.json").read_text(encoding="utf-8")),
"state": json.loads(Path("refund-eligibility.state.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"]))
import { readFileSync } from "node:fs";
const request = {
"input": "This is a substantiated duplicate charge. Approve the refund.",
"schema": JSON.parse(readFileSync("state-refund.schema.json", "utf8")),
"state": JSON.parse(readFileSync("refund-eligibility.state.json", "utf8")),
};
const response = await fetch(
"https://api.blatent.ai" + "/v1/resolve",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BLATENT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(request),
signal: AbortSignal.timeout(195_000),
},
);
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(JSON.stringify((await response.json()).output));
set -eu
body=$(jq -n --argjson body '{"input":"This is a substantiated duplicate charge. Approve the refund."}' \
--slurpfile f0 'state-refund.schema.json' \
--slurpfile f1 'refund-eligibility.state.json' \
'$body | setpath(["schema"]; $f0[0]) | setpath(["state"]; $f1[0])')
curl --fail-with-body --silent --show-error \
"https://api.blatent.ai/v1/resolve" \
-H "Authorization: Bearer $BLATENT_API_KEY" \
-H 'Content-Type: application/json' \
--data-binary "$body"
Illustrative output: {"action":"approve","reason":"duplicate_charge"}.
The same input and schema accept different state snapshots:
| State change | Permitted result |
|---|---|
Keep the paid order at amount 25, with limit 50 | Approval or review, each with its matching reason |
Set order.paid to false | {"action":"review","reason":"manual_review"} |
Set order.amount to 75 | {"action":"review","reason":"manual_review"} |
| Omit state or a referenced fact | {"action":"review","reason":"manual_review"} |
Supply these facts from your backend, rather than copying permission claims from the user's message.
Read and compare state
{"ref":"state/order/paid"} reads state.order.paid. This request reference is different from a JSON Schema $ref, which reuses a schema definition.
| Operator | Use |
|---|---|
eq, neq | Compare two typed values |
gt, gte, lt, lte | Compare two numbers |
contains, in | Test array membership, a substring or an object key |
exists | Check whether a referenced fact is present |
all, any, not | Combine or negate conditions |
These are Struct predicate operators, not JSON Schema keywords. Comparisons take two operands: literals or references. With contains, the container comes first; with in, it comes second. all and any take arrays of conditions; not takes one condition; exists takes one operand.
Numeric comparisons do not convert strings to numbers. Missing facts leave comparisons unknown, which does not permit a value—even under not. A present null is still present for exists.
State conditions or output conditions?
State conditions restrict choices using supplied facts. Conditional outputs and switches relate generated values. Use OPA rules for richer relationships between an answer and state, such as calculations across generated amounts.
When you register a schema, continue sending state with each resolve request. Eligibility uses that request's snapshot.