Skip to content
Developer previewBack to Kernall
Browse documentation
DocumentationUse Kernall

Use Kernall

Check an action

Call the gate before an operation, handle every decision, and keep retries tied to the same proposal.

Send a check

Send JSON to POST /api/check with an active project key in Authorization: Bearer <key>. X-API-Key is also accepted; prefer the Bearer header for new integrations.

Check a proposed action
curl --fail-with-body "$KERNALL_BASE_URL/api/check" \
  -H "Authorization: Bearer $KERNALL_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: quickstart-submit-1' \
  -d '{
    "action_type": "submit",
    "data": "Sample form content",
    "destination": "https://example.com/form",
    "task_context": "Testing a human review"
  }'
FieldRequiredLimit / behavior
action_typeYesNon-empty string, up to 64 characters; trimmed and lowercased.
dataNoString, up to 20,000 characters; evaluated in memory.
destinationNoString, up to 2,048 characters; URL or hostname. Needed when an allowlist is configured.
task_contextNoString, up to 1,000 characters; stored as context, not evaluated as a rule.

Optional fields may be omitted or null. Strings are trimmed; empty optional strings are absent. All policy verdicts return HTTP 200, including denials. Inspect the JSON decision.

Handle the result

Execute only on explicit approval. Stop on denial. On requires_human, retain the proposal and wait for its confirmation. Treat errors, timeouts, and unknown decisions as blocked actions.

Server-side JavaScript · integration outline
// Server-side JavaScript; keep the project key out of client bundles.
async function checkAction(action, stepId) {
  const response = await fetch(
    process.env.KERNALL_BASE_URL + "/api/check",
    {
      method: "POST",
      headers: {
        Authorization: "Bearer " + process.env.KERNALL_API_KEY,
        "Content-Type": "application/json",
        "Idempotency-Key": stepId,
      },
      body: JSON.stringify(action),
      signal: AbortSignal.timeout(10000),
    }
  );
  if (!response.ok) throw new Error("Check failed: HTTP " + response.status);
  const verdict = await response.json();
  if (!["approved", "denied", "requires_human"].includes(verdict.decision)) {
    throw new Error("Unexpected Kernall decision");
  }
  return verdict;
}

// A thrown error must leave the action blocked.
const verdict = await checkAction(
  { action_type: "navigate", destination: "https://example.com" },
  "agent-run-42-navigation-1"
);
if (verdict.decision === "approved") {
  // Execute the same proposed action here.
} else if (verdict.decision === "requires_human") {
  // Pause and poll the confirmation status. See Human approvals.
} else {
  // Stop the action.
}

Keep the action’s content and destination fixed while waiting. If either changes, make a new check. Kernall does not cryptographically bind the submitted data to your later tool call.

Retry without duplicate checks

Set Idempotency-Key to a stable identifier for one logical action, such as a run ID plus step ID. Keys are project-scoped and limited to 200 characters.

  • Reuse the key after a timeout or uncertain response.
  • Use a new key for another action or a fresh evaluation after a policy change.
  • A replay returns the original check with idempotent_replay: true.
  • The server does not compare bodies on replay. Reusing a key with different data still returns the old verdict.
  • A replay keeps the initial human-review verdict. Poll the confirmation for its final decision.

Rate limits and failures

The limit is 120 new evaluations per project per fixed minute window. Fresh check responses include X-RateLimit-Remaining. A 429 includes Retry-After in seconds.

Wait that interval before retrying. Use bounded backoff for transient failures and keep the action blocked. Recognized replays return before consuming an evaluation slot; concurrent retries can still touch the rate counter.

See API errors and troubleshooting.