Semantic validation
Validate JSON against business rules, not just schemas.
A request can be perfectly valid JSON, pass every type check, and still break your rules: wrong category, a reason that isn’t a reason, an address that doesn’t make sense for the customer. That’s semantic validation.
Syntax, logic, and meaning
Schema validation (JSON Schema, Zod, Valibot) answers “is this shaped correctly?”. It’s fast, deterministic, and should always run first.
Code answers precise logical questions: comparisons, lookups, sums. If a rule fits in an if, write the if.
Semantic validation answers questions about meaning that would take hundreds of brittle conditions to approximate: whether a description matches a category, whether a justification is plausible, whether a combination of fields looks right for this customer. That’s where Enforly fits.
Which validation for which rule
| Rule | JSON Schema / Zod | Code | Enforly |
|---|---|---|---|
amount is a positive number | ✓ Best fit | ✓ | Overkill |
amount ≤ 500 unless approved | — | ✓ Best fit | ✓ |
| Item name matches its category | — | Hard | ✓ Best fit |
| The reason is a real business reason | — | Not practical | ✓ Best fit |
| Shipping address looks like a package forwarder | — | Partial | ✓ Best fit |
Where the check fits
One call between the step that prepares something and the step that sends, publishes, or runs it. Your code applies the decision.
See it decide
The same policy evaluated on three cases. Pick one and run the check to see which evidence it relies on.
Policy
Line items must match their declared category. Items that contain lithium batteries or chemicals require hazmat: true, even when the name doesn’t say so. If the shipping address or delivery notes suggest a reshipping or package-forwarding service, send the order to review.
data
{"order": {"items": [{"sku": "BT-20","name": "Rechargeable Bluetooth speaker","category": "electronics","hazmat": true}],"shipping": {"address": "Av. Belgrano 1420, 3B, Córdoba","notes": "Ring 3B, I work from home"}}}
Result
Why: A rechargeable speaker has a battery and is flagged hazmat; the address and note look like a home delivery.
Your code: Accept and process the order.
Response JSON
{
"decision": "allow",
"allowed": true,
"violationProbability": 0.04,
"reviewProbability": 0.05,
"policyId": null,
"policyVersion": null,
"requestId": "req_7f3a…"
}Illustrative example. Real probabilities depend on your policy wording and the evidence you send.
allow · Consistent order
A rechargeable speaker has a battery and is flagged hazmat; the address and note look like a home delivery.
deny · Missing hazmat
The name never says “lithium”, but a 10,000 mAh portable charger is a lithium battery pack and must be flagged hazmat.
review · Looks like a forwarder
A warehouse suite with an account code and a “consolidate” note reads like a package forwarder, not the customer’s home.
Rules engines vs. natural-language rules
Some “natural language rules engines” translate your sentence into deterministic logic once and run that. It works when the rule maps to clear conditions. Enforly evaluates each request with the rule and the data together, which is what lets it handle rules that depend on interpretation. The trade-off is that decisions are probabilistic: you get violationProbability and review for uncertain cases, and you should test policies with real examples.
Set it up
- 1
Validate the shape first
Parse with Zod or JSON Schema and reject malformed input with a 400.
- 2
Keep exact rules in code
Limits, enums, and lookups are cheaper and fully predictable as code.
- 3
Send the rest to Enforly
Check the parsed object against rules that need judgment and return a 422 on deny.
import { z } from 'zod'
import { Enforly } from '@enforly/sdk'
const guard = new Enforly({ apiKey: process.env.ENFORLY_API_KEY })
const Order = z.object({
items: z.array(z.object({ sku: z.string(), name: z.string(), category: z.string(), hazmat: z.boolean() })),
shipping: z.object({ address: z.string(), notes: z.string() }),
})
app.post('/orders', async (c) => {
const parsed = Order.safeParse(await c.req.json())
if (!parsed.success) return c.json({ error: parsed.error.issues }, 400)
const result = await guard.check({ policyId: ORDER_RULES_ID, data: { order: parsed.data } })
if (result.decision === 'deny') return c.json({ error: 'ORDER_RULE_VIOLATION', requestId: result.requestId }, 422)
const order = await orders.create(parsed.data, { hold: result.decision === 'review' })
return c.json(order, 201)
})Full request and response fields are in the API reference. Using a coding agent? Point it to Build with AI.
What Enforly doesn’t do
Enforly evaluates the information you send and returns a decision. It doesn’t run operations, and it doesn’t guarantee safety or compliance on its own.
- Enforly is not a replacement for schema validation. Validate types first.
- Decisions are model-based. For rules where a wrong answer is unacceptable and the logic is exact, use code.
- Each check is a network call; don’t use it for per-field validation in tight loops.
Questions
Is the result deterministic?
Not in the way a schema is. Enforly returns a decision and estimated probabilities. Use review for uncertain cases and test your policy with real examples before relying on it.
What JSON can I send?
Any JSON value in data, up to 32,768 characters of input in total.
Related
Try this policy on your own data.
Copy the policy above, add your real examples, and see the decision. 200 free checks per month, no credit card.