SDK & Integration

The Koladr SDK is how your agents communicate with the control plane. This guide covers installation, the core API methods, and a complete real-world example.

How the SDK Works

The SDK provides a thin client that sends structured data to the Koladr API. Your agent uses four primary methods:

  1. startRun(): opens a new run session
  2. logEvents(): records events during the run
  3. requestAction(): requests permission to take an action and (when allowed) executes it through the connector
  4. endRun(): closes the run with a final status

Every call is authenticated with your agent API key. The SDK attaches the key to each request, serializes the body as JSON, and surfaces typed errors via KoladrError.

Installation

npm install @koladr/sdk

The SDK is a TypeScript-first package with full type definitions. It works in Node.js, Bun, and any server-side JavaScript runtime.

Initialization

koladr.ts
import { KoladrClient } from "@koladr/sdk";

const koladr = new KoladrClient(
  process.env.KOLADR_BASE_URL!, // e.g. "https://app.koladr.com"
  process.env.KOLADR_AGENT_KEY!,
);

The constructor takes two positional arguments: the Koladr base URL (the host where your dashboard lives) and the agent API key you generated in Settings → API Keys. Treat the API key like a password — load it from environment variables or a secrets manager and never commit it to source control.

Starting a Run

Call startRun() at the beginning of every agent task. Provide a triggerType (what initiated the run); you can also include an externalRef to correlate the run with a record in your own system, an agentVersion for change tracking, and a triggeredBy identifier.

start-run.ts
const { runId, status } = await koladr.startRun({
  triggerType: "support-ticket",
  externalRef: "TKT-1042",
  agentVersion: "1.4.2",
});

console.log("Run started:", runId, "status:", status);

The response is { runId, status }. Use runId in all subsequent logEvents(), requestAction(), and endRun() calls.

Logging Events

Use logEvents() to record what your agent is doing during a run. Events build the timeline that operators and approvers see in the dashboard.

log-events.ts
// Koladr reserves sequenceNo: 1 for the run-started event,
// so your first agent event should start at 2.
await koladr.logEvents(runId, [
  {
    eventType: "context.retrieved",
    sequenceNo: 2,
    payload: {
      source: "knowledge-base",
      documentsFound: 3,
      relevanceScore: 0.92,
    },
  },
  {
    eventType: "intent.classified",
    sequenceNo: 3,
    payload: {
      intent: "refund-request",
      confidence: 0.95,
      reason: "Customer reported defective product",
    },
  },
]);

Each event needs a unique, monotonic sequenceNo within the run. Koladr reserves 1 for the run-started event it inserts in startRun(), so your first agent event should start at 2. The occurredAt field is optional and defaults to the server time when the event lands.

Log generously

The more context you log, the easier it is for approvers to make decisions and for operators to investigate incidents. Include confidence scores, sources, and reasoning.

Requesting an Action

When your agent needs to perform a real-world action, use requestAction(). This is the critical control point where Koladr evaluates policies and (when the policy allows) dispatches the action through the matching connector.

request-action.ts
const result = await koladr.requestAction({
  runId,
  actionType: "refund.create",
  provider: "stripe",
  resourceType: "charge",
  resourceId: "ch_3OabcEXAMPLE",
  payload: {
    charge: "ch_3OabcEXAMPLE",
    amount: 7999, // Stripe amounts are in cents (this is $79.99)
    reason: "requested_by_customer",
  },
  evidence: {
    confidence: 0.92,
    customerTier: "premium",
    orderAge: "3 days",
  },
});

Field reference:

  • runId: the run this action belongs to.
  • actionType: the action string (e.g. refund.create, email.send, ticket.update). See Connectors for the full list each provider executes.
  • provider: which connector should run the action — currently stripe, gmail, hubspot, or zendesk.
  • payload: the action-specific arguments. The policy engine exposes these as payload.* in conditions.
  • evidence (optional): risk signals like model confidence, retrieved sources, or customer tier. Exposed as evidence.* in conditions.
  • resourceType / resourceId (optional): a stable handle for the external resource being acted on. Useful for the audit trail.

Handling Responses

Every requestAction() call resolves to a { decision, reason, ... } object. The shape of the rest of the object depends on the decision:

DecisionExtra fieldsWhat happened
allowexecutionPolicy allowed the action and the connector executed it.
allow_with_alertexecutionSame as allow, plus an alert event was recorded.
require_approvalapprovalRequestIdAction is paused; an approver must decide before it runs.
blockA policy blocked the action; reason explains why.
handle-response.ts
switch (result.decision) {
  case "allow":
  case "allow_with_alert":
    // Policy allowed the action; the connector executed it.
    // result.execution is the action_executions row.
    if (result.execution.status === "executed") {
      console.log("Refund processed:", result.execution.response_payload);
    } else {
      console.error("Connector failed:", result.execution.response_payload);
    }
    break;

  case "require_approval":
    // A human must approve before the connector runs.
    // Use result.approvalRequestId to poll or correlate.
    console.log("Pending approval:", result.approvalRequestId);
    break;

  case "block":
    // A policy blocked this action. result.reason explains why.
    console.log("Blocked:", result.reason);
    // Handle gracefully: inform the customer, escalate, etc.
    break;
}

When execution is present, it's the persisted action_executionsrow, including the connector'sstatus ("executed" or "failed") and the connector'sresponse_payload.

Ending a Run

Always end runs, even when errors occur. This keeps your run history clean and lets operators distinguish between completed and abandoned runs.

end-run.ts
await koladr.endRun({
  runId,
  status: "completed", // or "failed" | "blocked"
});

The status field accepts "completed", "failed", or "blocked". If a request was routed to require_approval, leave the run open — Koladr transitions it to awaiting_approval and resumes when the approver decides.

Full Example: Support Agent Refund Flow

Here is a complete example showing a support agent handling a refund request through Koladr:

support-agent.ts
import { KoladrClient, KoladrError } from "@koladr/sdk";

const koladr = new KoladrClient(
  process.env.KOLADR_BASE_URL!,
  process.env.KOLADR_AGENT_KEY!,
);

async function handleSupportTicket(ticket: {
  id: string;
  customerTier: string;
  message: string;
}) {
  // 1. Start a run.
  const { runId } = await koladr.startRun({
    triggerType: "support-ticket",
    externalRef: ticket.id,
  });

  let nextSeq = 2; // sequenceNo: 1 is reserved for run_started.

  try {
    // 2. Log context retrieval.
    await koladr.logEvents(runId, [
      {
        eventType: "context.retrieved",
        sequenceNo: nextSeq++,
        payload: { source: "knowledge-base", query: ticket.message },
      },
    ]);

    // 3. Agent determines a refund is appropriate.
    await koladr.logEvents(runId, [
      {
        eventType: "intent.classified",
        sequenceNo: nextSeq++,
        payload: { intent: "refund-request", confidence: 0.95 },
      },
    ]);

    // 4. Request the refund through Koladr.
    const result = await koladr.requestAction({
      runId,
      actionType: "refund.create",
      provider: "stripe",
      payload: {
        charge: "ch_3OabcEXAMPLE",
        amount: 7999, // $79.99 in cents
        reason: "requested_by_customer",
      },
      evidence: {
        confidence: 0.95,
        customerTier: ticket.customerTier,
      },
    });

    // 5. Record the outcome and end the run.
    if (result.decision === "allow" || result.decision === "allow_with_alert") {
      await koladr.logEvents(runId, [
        {
          eventType: "action.completed",
          sequenceNo: nextSeq++,
          payload: { execution: result.execution },
        },
      ]);
      await koladr.endRun({ runId, status: "completed" });
    } else if (result.decision === "require_approval") {
      await koladr.logEvents(runId, [
        {
          eventType: "action.pending",
          sequenceNo: nextSeq++,
          payload: { approvalRequestId: result.approvalRequestId },
        },
      ]);
      // Leave the run in "awaiting_approval"; Koladr will resume it
      // when the approver decides.
    } else {
      await koladr.logEvents(runId, [
        {
          eventType: "action.blocked",
          sequenceNo: nextSeq++,
          payload: { reason: result.reason },
        },
      ]);
      await koladr.endRun({ runId, status: "blocked" });
    }
  } catch (error) {
    if (error instanceof KoladrError && error.isUnauthorized) {
      // Agent key is missing, unknown, or revoked. The API returns
      // the same 401 for all three (constant-time, no enumeration);
      // regenerate the key in the dashboard if the issue persists.
    }
    await koladr.endRun({ runId, status: "failed" });
    throw error;
  }
}

REST API

If you are not using TypeScript, you can call the Koladr API directly over HTTP. The SDK methods map 1:1 to REST endpoints (/api/ingest/run-start, /api/ingest/events, /api/actions/request, /api/ingest/run-end) and each request body includes the agent API key as agentKey.

Error Handling

The SDK throws a single typed error class, KoladrError, for every non-2xx response. The error carries the HTTP status, an optional server-suppliedcode, and one convenience getter:

  • error.isUnauthorized: HTTP 401. The agent key is missing, unknown, or has been revoked.

One 401 for every auth failure

The Koladr API intentionally returns the same response shape for missing, unknown, and revoked keys — differentiating them in the public response would let an attacker enumerate which key prefixes correspond to live agents. The dashboard surfaces the specific reason (active vs revoked) to authenticated operators on the Settings → API keys page; treat any 401 as “regenerate the key in the dashboard”. The deprecated error.isRevoked getter is kept for backward compatibility but now aliases error.isUnauthorized.
error-handling.ts
import { KoladrClient, KoladrError } from "@koladr/sdk";

try {
  await koladr.requestAction({ /* ... */ });
} catch (error) {
  if (error instanceof KoladrError) {
    if (error.isUnauthorized) {
      // 401 — agent key is missing, unknown, or revoked.
      // Generate a new key in the Koladr dashboard and rotate.
    } else if (error.status === 402) {
      // Workspace billing is locked (past_due grace exceeded,
      // expired trial, canceled). Surface the recovery summary.
    } else if (error.status === 429) {
      // Rate-limited — back off and retry.
    } else {
      // Generic Koladr error: error.status, error.code, error.message.
    }
  } else {
    throw error;
  }
}

Wrap your Koladr calls in try/catch and always end the run in a finally block to prevent orphaned runs.

Next

Policies

Define rules for how agent actions are governed