Developer documentation

Govern an action in five minutes.

Your model proposes an action. AgentLedger evaluates its authority. Your code executes only after an allow or approved outcome.

Five-minute quickstart

  1. Create a workspace at app.agentledgersecurity.com.
  2. Register an agent, such as refund-copilot, and add its allowed actions, such as issue_refund.
  3. Generate a scoped API key. Store it as AGENTLEDGER_API_KEY on your server.
  4. Create or confirm a policy. The default safe policy requires approval for refunds over $250.
  5. Evaluate immediately before your executor runs.
const response = await fetch("https://app.agentledgersecurity.com/api/evaluate", {
  method: "POST",
  headers: {
    "content-type": "application/json",
    "x-agentledger-key": process.env.AGENTLEDGER_API_KEY
  },
  body: JSON.stringify({
    agent: "refund-copilot",
    action: "issue_refund",
    amount: 40000,
    payment_intent: "pi_..."
  })
});

const result = await response.json();

if (result.decision === "allow") await executeAction();
if (result.decision === "block") throw new Error(result.reason);
if (result.decision === "require_approval") {
  console.log("Waiting for", result.approval_id);
}
Amounts are integer cents

40000 represents $400.00. Evaluate as close as possible to execution and never treat require_approval as permission to continue.

Evaluation API

POST/api/evaluate

Headers

HeaderValue
content-typeapplication/json
x-agentledger-keyYour scoped AgentLedger API key

Request body

FieldRequiredDescription
actionYesAction to evaluate; must be allowed for the scoped agent.
agentNoAgent slug. If provided, it must match the key scope.
amountNoMonetary amount in integer cents.
countNoQuantity used by count-based policies.
payment_intentNoStripe PaymentIntent reference associated with a refund action.

Response

{
  "decision": "require_approval",
  "reason": "Refund exceeds the default $250 autonomous limit.",
  "evaluation_id": "0b6d8edb-...",
  "approval_id": "3d814f38-...",
  "agent": { "id": "...", "slug": "refund-copilot" },
  "policy": "DEFAULT-SAFE",
  "usage": { "plan": "pro", "used": 1, "limit": 50000, "remaining": 49999 },
  "evaluated_at": "2026-08-29T01:02:32.805Z"
}

decision is one of allow, block, or require_approval. approval_id is null unless approval is required. Usage headers x-agentledger-limit and x-agentledger-remaining are also returned.

Approval status

Poll by evaluation ID, not approval ID.

GET/api/evaluations/{evaluation_id}
const status = await fetch(
  "https://app.agentledgersecurity.com/api/evaluations/" + result.evaluation_id,
  { headers: { "x-agentledger-key": process.env.AGENTLEDGER_API_KEY } }
).then(r => r.json());

if (status.terminal && status.approval_status === "executed") {
  console.log(status.stripe_refund_id);
}

The terminal approval statuses are executed, denied, and execution_failed.

Webhook signature verification

Configure a public HTTPS endpoint in AgentLedger. Deliveries include x-agentledger-event, x-agentledger-event-id, x-agentledger-delivery, x-agentledger-attempt, and x-agentledger-signature. The signature format is t=timestamp,v1=hex_digest.

import crypto from "node:crypto";

function verifyAgentLedger(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map(part => part.trim().split("=", 2))
  );
  const timestamp = Number(parts.t);
  if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(timestamp + "." + rawBody)
    .digest("hex");

  const supplied = Buffer.from(parts.v1 || "", "hex");
  const wanted = Buffer.from(expected, "hex");
  return supplied.length === wanted.length && crypto.timingSafeEqual(supplied, wanted);
}

Verify against the exact raw UTF-8 request body before parsing JSON. Reject stale timestamps and duplicate delivery IDs. Return a 2xx response promptly; AgentLedger retries retryable failures up to three attempts.

Events

EventMeaning
approval.executedApproved action executed successfully.
approval.deniedAn approver denied the action.
approval.execution_failedApproval succeeded but the configured execution failed.
{
  "id": "webhook-event-uuid",
  "type": "approval.executed",
  "created_at": "2026-08-29T01:04:10.000Z",
  "data": {
    "approval_id": "...",
    "event_id": "evaluation-id",
    "decision": "approved",
    "decided_by": "approver@example.com",
    "stripe_refund_id": "re_...",
    "stripe_status": "succeeded"
  }
}

OpenAI, Claude, and Gemini

AgentLedger is provider-neutral. First ask the model for a structured proposal, validate it in your application, then send the normalized action to AgentLedger. Never give a model direct executor credentials.

Shared governance helper

async function govern(proposal) {
  return fetch("https://app.agentledgersecurity.com/api/evaluate", {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-agentledger-key": process.env.AGENTLEDGER_API_KEY
    },
    body: JSON.stringify({ agent: "refund-copilot", ...proposal })
  }).then(r => r.json());
}

OpenAI

const response = await openai.responses.create({
  model: "gpt-5.6-luna",
  input: customerMessage,
  text: { format: refundProposalFormat }
});
const decision = await govern(JSON.parse(response.output_text));

Claude

const message = await anthropic.messages.create({
  model: process.env.CLAUDE_MODEL,
  max_tokens: 500,
  tools: [refundProposalTool],
  messages: [{ role: "user", content: customerMessage }]
});
const proposal = message.content.find(item => item.type === "tool_use").input;
const decision = await govern(proposal);

Gemini

const response = await gemini.models.generateContent({
  model: process.env.GEMINI_MODEL,
  contents: customerMessage,
  config: { responseMimeType: "application/json", responseSchema: refundSchema }
});
const decision = await govern(JSON.parse(response.text));

SDK surfaces and model names evolve. Keep provider-specific parsing in a small adapter and keep the AgentLedger request stable.

Errors and usage limits

StatusMeaning
400Invalid request, such as a missing action.
401Missing, invalid, or revoked AgentLedger key.
403Disabled agent, wrong key scope, or action not permitted.
404Evaluation not found for this key and agent.
429Monthly evaluation allowance reached. Response code: usage_limit_reached.