JavaScript SDK
Call the Check API from server-side JavaScript or TypeScript. Pass what you want to check and the evidence your policy needs, then continue only on an allow decision.
Using a coding agent? Install the Enforly skill so it follows these rules.
Install and check
Requires Node.js 20 or newer. Keep ENFORLY_API_KEY in your server environment, never in browser code. The default API origin is https://api.enforly.com.
npm install @enforly/sdkimport { Enforly } from '@enforly/sdk'
const guard = new Enforly({ apiKey: process.env.ENFORLY_API_KEY })
// Keep the original parts supplied by your chat framework.
const conversation = messages.map(({ role, parts }) => ({ role, parts }))
const toolCall = pendingToolCall
const userConfirmed = approvalStore.hasApproved(toolCall.id)
try {
const result = await guard.check({
policy: 'Never run destructive database operations without explicit user confirmation.',
data: { action: toolCall, conversation, userConfirmed },
})
if (result.allowed) await executeTool(toolCall)
else await pauseOrReject(toolCall, result.decision)
} catch (error) {
// A failed check never authorizes the action.
await pauseOrReject(toolCall, 'error')
}What goes in data?
data is free-form JSON, not an Enforly schema. It can be a string, array, object, number, boolean, or null. Include what you want to check and the evidence the policy refers to: a proposed tool call, a refund request, a post before publishing, an assistant reply, relevant conversation, or an approval recorded by your application. The names action, conversation, and userConfirmed in these examples are your own JSON keys.
Chat frameworks often represent a conversation as messages with role and parts. Parts may contain text, reasoning (when available), tool calls, and tool results. Pass the relevant parts in the format your framework already provides; Enforly does not require or parse a specific parts format. Keep binary data, secrets, and unnecessary history out of the check. For an approval policy, use confirmation from trusted application state rather than a claim made by the model.
Use saved policies
Use a policy ID from Policies. Without a version, a check uses the version marked active there: saving a rule change activates the new version unless you save it as a draft, and you can roll back from the policy details. Renaming a policy does not create a version. A deactivated policy stays listed for reference, and checks that use its ID return POLICY_NOT_FOUND. Presets stay on the version you started with until you activate a newer one. Add policyVersion, or { id, version } in policyIds, to pin an exact version in code. policyIds returns one result per policy; the overall decision is deny if any denies, otherwise review if any needs review.
const evidence = { action: toolCall, conversation, userConfirmed }
// One saved policy (private or platform preset).
const single = await guard.check({ policyId: databasePolicyId, data: evidence })
// Several saved policies, evaluated in one request.
const combined = await guard.check({
policyIds: [databasePolicyId, disclosurePolicyId],
data: evidence,
})
if (combined.allowed) await executeTool(toolCall)Check at the right point
The same method can inspect different stages of a chatbot or agent. Build data from the values available at that stage, and run each check before the guarded step.
// Before the model sees a user prompt.
const promptCheck = await guard.check({
policyId: promptPolicyId,
data: { prompt: userPrompt, conversation },
})
// Before an assistant reply reaches the user.
const replyCheck = await guard.check({
policyId: disclosurePolicyId,
data: { response: assistantReply, conversation },
})
// Before a tool result is sent back into the agent loop.
const outputCheck = await guard.check({
policyId: outputPolicyId,
data: { output: toolOutput, action: toolCall, conversation },
})Read the result
decisionallow, deny, or review. Review means pause for a person or gather more evidence: the policy hands this case to a person and that approval is missing, or the evidence is unclear.
allowedtrue only for allow. Gate the guarded action on this field.
violationProbability / reviewProbabilityEstimated probabilities, 0–1, that the data breaks the policy, or that it needs a review or approval the policy asks for and data does not include. Present on a single-policy result and inside each multi-policy result.
policyId / policyVersionIdentifies the saved policy evaluated. Both are null for an inline policy.
resultsFor policyIds only: one entry per policy with policyId, policyVersion, decision, and both probabilities.
requestIdCorrelation ID for activity and support. The activity log does not store your data payload.
An authentication, validation, rate limit, network, or service error is never an allow decision. With the SDK, guard.check throws an EnforlyError with code, status, and requestId; with the API, handle a non-2xx response before reading a decision. Stop the guarded step when a check fails.
Resolve a review
Enforly keeps no state between checks; each one judges only its own data. To resolve a review, collect the approval the policy names, then check the original operation again with that approval in data. Do not check the approval alone, such as a reply of “yes”, as if it were a new request, and never let a follow-up skip the check.
Write in the policy who can approve. “Requires review” or “a person decides” means someone other than the requester, so the requester’s own confirmation keeps returning review. If that confirmation is enough, say so: Before deleting records, require the user’s explicit confirmation of this specific deletion. If another person must approve, name the role and where the approval is recorded: Refunds above 200 USD require a manager approval recorded in the ticket. Take approvals from trusted application state, not from claims inside the evaluated content.
// The first check returned review. A manager approved the refund in your app.
const result = await guard.check({
policy: 'Refunds above 200 USD require a manager approval recorded in the ticket.',
data: {
action: refund, // the same operation as the first check
approval: { by: 'manager', userId: approver.id, approved: true, at: approvedAt },
},
})
if (result.allowed) await issueRefund(refund)