Evaluate Policy from Source
Compile and evaluate a CNL (Controlled Natural Language) policy source string on the fly, without requiring the policy to be deployed first. Optionally returns a full decision trace for debugging.
Endpoint
POST /api/v1/policies/evaluate-source
Required Role
MEMBER
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
trace | boolean | false | When true, the response includes a structured decisionTrace object showing which rules fired and how the result was derived. |
Headers
| Header | Value | Required |
|---|---|---|
Content-Type | application/json | Yes |
X-Tenant-Id | Tenant identifier string | Yes |
X-Aster-Signature | HMAC-SHA256 hex signature | Yes |
X-Aster-Nonce | Random string (16+ bytes hex or UUID) | Yes |
X-Aster-Timestamp | Unix timestamp in milliseconds | Yes |
X-User-Role | Caller's role (MEMBER, ADMIN, OWNER) | Yes |
X-User-Id | Caller identifier for audit logs | No |
See Authentication for full details on computing the HMAC signature.
Request Body
json
{
"source": "string",
"context": {},
"locale": "en-US",
"functionName": "evaluate"
}| Field | Type | Required | Description |
|---|---|---|---|
source | string | Yes | Raw CNL policy source text. The engine parses, canonicalizes, and compiles this string before evaluation. Parsing errors are returned in the error field. |
context | object | Yes | A single context object whose keys map to the declared parameter names of the target function. For multi-parameter functions, wrap each argument under its declared parameter name. |
locale | string | No | BCP 47 locale tag that identifies the CNL keyword set used in source. Defaults to "en-US". Use "zh-CN" for Simplified Chinese CNL syntax. |
functionName | string | No | Name of the function to invoke within the compiled module. Defaults to "evaluate". If the source defines only one function, that function is called regardless of this value. |
Response Body
json
{
"result": "<any>",
"executionTimeMs": 0,
"error": null,
"decisionTrace": null
}| Field | Type | Description |
|---|---|---|
result | any | The value returned by the evaluated function. |
executionTimeMs | number | Total wall-clock time in milliseconds including compilation and execution. |
error | string | null | Parse, compile, or runtime error message; null on success. |
decisionTrace | object | null | Present only when ?trace=true is set. Contains the module name, function name, and an ordered list of trace steps recording rule evaluation. null when tracing is disabled. |
Decision Trace Structure
When ?trace=true is used, decisionTrace has the following shape:
json
{
"moduleName": "string",
"functionName": "string",
"steps": [
{
"sequence": 1,
"expression": "string",
"result": "<any>",
"matched": true,
"children": []
}
],
"finalResult": "<any>",
"executionTimeMs": 0
}| Field | Type | Description |
|---|---|---|
moduleName | string | The name of the module that was evaluated. |
functionName | string | The name of the function that was invoked. |
steps | TraceStep[] | Ordered list of evaluation steps. |
finalResult | any | The final result of the evaluation. |
executionTimeMs | number | Evaluation time in milliseconds. |
Each TraceStep contains:
| Field | Type | Description |
|---|---|---|
sequence | number | Step number (starting from 1). |
expression | string | The rule or expression description. |
result | any | The evaluated result of this step. |
matched | boolean | Whether this branch was the final match. |
children | TraceStep[] | Nested sub-steps for branching logic (if/else, match). |
HTTP Status Codes
| Status | Meaning |
|---|---|
200 OK | Compilation and evaluation attempted. Check error for compile-time or runtime failures. |
400 Bad Request | Malformed request body, missing required fields, or invalid X-Tenant-Id. |
401 Unauthorized | Missing or invalid HMAC signature headers, or timestamp outside the 5-minute replay window. |
403 Forbidden | HMAC is valid but the caller lacks the MEMBER role. |
409 Conflict | Nonce has already been used within the replay window. |
500 Internal Server Error | Unexpected engine failure. |
Examples
Without Trace
bash
TENANT_ID="acme-corp"
TIMESTAMP=$(($(date +%s) * 1000))
NONCE=$(openssl rand -hex 16)
BODY='{"source":"Module Discount.\n\nRule calculate given order as Order, produce Int:\n If order.total greater than 100\n Return 10.\n Return 0.","context":{"order":{"total":150}},"locale":"en-US","functionName":"calculate"}'
API_SECRET="your-api-secret-here"
METHOD="POST"
PATH_URI="/api/v1/policies/evaluate-source"
QUERY=""
BODY_HASH=$(printf '%s' "${BODY}" | openssl dgst -sha256 | awk '{print $2}')
CANONICAL="${METHOD}|${PATH_URI}|${QUERY}|${TIMESTAMP}|${NONCE}|${BODY_HASH}"
SIGNATURE=$(printf '%s' "${CANONICAL}" | openssl dgst -sha256 -hmac "${API_SECRET}" | awk '{print $2}')
curl -X POST "https://policy.aster-lang.dev${PATH_URI}" \
-H "Content-Type: application/json" \
-H "X-Tenant-Id: ${TENANT_ID}" \
-H "X-User-Role: MEMBER" \
-H "X-Aster-Signature: ${SIGNATURE}" \
-H "X-Aster-Nonce: ${NONCE}" \
-H "X-Aster-Timestamp: ${TIMESTAMP}" \
-d "${BODY}"js
const source = `Module Discount.
Define Order has total as Int.
Rule calculate given order as Order, produce Int:
If order.total greater than 100
Return 10.
Return 0.`
const body = JSON.stringify({
source,
context: { order: { total: 150 } },
locale: 'en-US',
functionName: 'calculate',
})
const response = await fetch(
'https://policy.aster-lang.dev/api/v1/policies/evaluate-source',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Tenant-Id': 'acme-corp',
'X-User-Role': 'MEMBER',
'X-Aster-Signature': signature, // see Authentication docs
'X-Aster-Nonce': nonce,
'X-Aster-Timestamp': timestamp,
},
body,
}
)
const data = await response.json()With Decision Trace
bash
TENANT_ID="acme-corp"
TIMESTAMP=$(($(date +%s) * 1000))
NONCE=$(openssl rand -hex 16)
BODY='{"source":"Module Discount.\n\nDefine Order has total as Int.\n\nRule calculate given order as Order, produce Int:\n If order.total greater than 100\n Return 10.\n Return 0.","context":{"order":{"total":150}},"locale":"en-US","functionName":"calculate"}'
API_SECRET="your-api-secret-here"
METHOD="POST"
PATH_URI="/api/v1/policies/evaluate-source"
QUERY="trace=true"
BODY_HASH=$(printf '%s' "${BODY}" | openssl dgst -sha256 | awk '{print $2}')
CANONICAL="${METHOD}|${PATH_URI}|${QUERY}|${TIMESTAMP}|${NONCE}|${BODY_HASH}"
SIGNATURE=$(printf '%s' "${CANONICAL}" | openssl dgst -sha256 -hmac "${API_SECRET}" | awk '{print $2}')
curl -X POST "https://policy.aster-lang.dev${PATH_URI}?${QUERY}" \
-H "Content-Type: application/json" \
-H "X-Tenant-Id: ${TENANT_ID}" \
-H "X-User-Role: MEMBER" \
-H "X-Aster-Signature: ${SIGNATURE}" \
-H "X-Aster-Nonce: ${NONCE}" \
-H "X-Aster-Timestamp: ${TIMESTAMP}" \
-d "${BODY}"js
const source = `Module Discount.
Define Order has total as Int.
Rule calculate given order as Order, produce Int:
If order.total greater than 100
Return 10.
Return 0.`
const body = JSON.stringify({
source,
context: { order: { total: 150 } },
locale: 'en-US',
functionName: 'calculate',
})
const response = await fetch(
'https://policy.aster-lang.dev/api/v1/policies/evaluate-source?trace=true',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Tenant-Id': 'acme-corp',
'X-User-Role': 'MEMBER',
'X-Aster-Signature': signature, // see Authentication docs
'X-Aster-Nonce': nonce,
'X-Aster-Timestamp': timestamp,
},
body,
}
)
const data = await response.json()
// data.decisionTrace.steps → array of rule evaluation stepsExample Response (trace=true)
json
{
"result": 10,
"executionTimeMs": 12,
"error": null,
"decisionTrace": {
"moduleName": "Discount",
"functionName": "calculate",
"steps": [
{
"sequence": 1,
"expression": "order.total greater than 100",
"result": 10,
"matched": true,
"children": []
}
],
"finalResult": 10,
"executionTimeMs": 8
}
}