SDKs

TypeScript SDK

The official SDK is a thin, dependency-free wrapper over the v1 REST API with typed methods, automatic retries on transient errors, a polling helper, and webhook verification. It runs on Node 18+ and in any runtime with a global fetch.

Other languages
There is no Python SDK yet. The API is plain JSON over HTTPS, so any HTTP client works — every method below maps to one endpoint in the API reference.

Install#

bash
npm install @loopllama/sdk

Initialize#

Construct a client with your API key. Read it from the environment rather than hard-coding it.

ts
import { LoopLlama } from "@loopllama/sdk";

const ll = new LoopLlama({
  apiKey: process.env.LOOPLLAMA_API_KEY!,
  // baseUrl defaults to https://loopllama.ai/api/v1
  // Self-hosting? baseUrl: "https://your-host/api/v1"
});

Workflows#

Create, list, and retrieve workflows. Pass a crew to override the default planner→writer pipeline.

ts
// Create
const workflow = await ll.workflows.create({
  name: "Spec reviewer",
  crew: [
    { role: "researcher", systemPrompt: "Extract key claims and open questions." },
    { role: "writer", systemPrompt: "Draft a one-page summary with a Risks section." },
  ],
});

// List
const workflows = await ll.workflows.list();

// Retrieve
const wf = await ll.workflows.get(workflow.id);

Runs#

Trigger and poll

poll() handles the retrieve loop for you, resolving once the run reaches completed, failed, or pauses in waiting_input so you can respond.

ts
const run = await ll.runs.create(workflow.id, {
  input: "Summarize the attached spec and propose 3 risks.",
});

const result = await ll.runs.poll(run.id, {
  intervalMs: 2000,           // default
  timeoutMs: 10 * 60_000,     // default
  onUpdate: (r) => console.log(r.status, r.total_steps),
});

if (result.status === "completed") {
  console.log(result.output);
} else if (result.status === "failed") {
  console.error(result.error);
}

Respond to a paused run

When a workflow has human-in-the-loop enabled, a run can pause with status: "waiting_input" and a pending object describing the question or the action awaiting approval.

ts
if (result.status === "waiting_input") {
  if (result.pending?.kind === "input") {
    await ll.runs.respond(run.id, { action: "input", input: "Use the Q3 numbers." });
  } else {
    await ll.runs.respond(run.id, { action: "approve" });
    // or: { action: "reject", reason: "Wrong recipient" }
  }
  const finished = await ll.runs.poll(run.id);
}

Retrieve steps

ts
const full = await ll.runs.get(run.id);
for (const step of full.steps ?? []) {
  console.log(`[${step.order}] ${step.role}: ${step.tokens_in}/${step.tokens_out} tokens`);
}

Webhooks#

In production, prefer webhooks over polling. Register an endpoint, then verify each delivery's signature with the helper. See the webhooks guide for the payload shape and retry behaviour.

ts
// Register (the secret is returned exactly once — store it)
const endpoint = await ll.webhooks.create({
  url: "https://example.com/loopllama/webhook",
  events: ["run.completed", "run.failed", "run.waiting_input"],
});
console.log(endpoint.secret); // whsec_...

// Receive (Express) — verify against the RAW body
import express from "express";
import { constructWebhookEvent, LoopLlamaError } from "@loopllama/sdk";

app.post("/loopllama/webhook", express.raw({ type: "application/json" }), (req, res) => {
  try {
    const event = constructWebhookEvent(
      req.body.toString("utf8"),
      req.header("LoopLlama-Signature"),
      process.env.LOOPLLAMA_WEBHOOK_SECRET!,
    );
    if (event.type === "run.completed") {
      console.log(event.data.run.output);
    }
    res.sendStatus(200);
  } catch (err) {
    if (err instanceof LoopLlamaError) return res.sendStatus(400);
    throw err;
  }
});

Error handling#

SDK methods throw a typed LoopLlamaError carrying the HTTP status and the API error message. Timeouts throw LoopLlamaTimeoutError. See Errors for the full list of codes.

ts
import { LoopLlama, LoopLlamaError } from "@loopllama/sdk";

try {
  await ll.runs.create(workflowId, { input: "" });
} catch (err) {
  if (err instanceof LoopLlamaError) {
    console.error(err.status, err.message); // 400 "String must contain at least 1 character(s)"
  }
}

Options#

  • baseUrl — override for self-hosted deployments.
  • maxRetries — retries for GET requests on 429, 5xx, and network errors (default 2, exponential backoff). Writes are never retried automatically.
  • timeoutMs — per-request timeout (default 30 s).
  • fetch — supply a custom fetch for tests or older runtimes.
Prefer raw HTTP?
The SDK is a thin layer over the REST API — every method maps to an endpoint in the API reference. You can call the API directly from any language with an HTTP client.