Stevora

Quickstart

Get up and running with Stevora in 5 minutes

Quickstart

This guide walks you through installing the Stevora SDK, defining a workflow, running it, and inspecting the result. By the end you will have a working workflow that researches a company, drafts a summary, and logs the output.

Prerequisites

  • Node.js 18+
  • A running Stevora instance (local or hosted) with an API key

If you are self-hosting, run npx prisma db seed after starting the server to generate a demo workspace and API key. See Self-Hosting for full setup instructions.

Install the SDK

npm install @stevora/sdk

Initialize the client

Create a file called run.ts and initialize the AgentRuntime client with your API key and the base URL of your Stevora instance.

run.ts
import { AgentRuntime } from '@stevora/sdk';

const runtime = new AgentRuntime({
  apiKey: process.env.STEVORA_API_KEY!,   // e.g. "stv_k1_a3f8..."
  baseUrl: 'http://localhost:3000',        // your Stevora instance URL
});

The AgentRuntime constructor accepts an AgentRuntimeConfig object:

ParameterTypeRequiredDescription
apiKeystringYesYour workspace API key
baseUrlstringNoStevora instance URL (defaults to localhost:3000)
timeoutnumberNoRequest timeout in milliseconds

Create a workflow definition

A workflow definition declares the sequence of steps Stevora will execute. Use runtime.definitions.create() to register one. The example below defines three steps: an LLM step that researches a company, a second LLM step that drafts a summary, and a task step that finalizes the output.

run.ts
const definition = await runtime.definitions.create({
  name: 'company-research',
  version: '1',
  description: 'Research a company and draft a summary',
  steps: [
    {
      type: 'llm',
      name: 'research-company',
      model: 'claude-sonnet-4-20250514',
      systemPrompt:
        'You are a business research assistant. Given a company name, return a JSON object with key facts.',
      messages: [
        {
          role: 'user',
          content:
            'Research this company: {{input.company}}. Return JSON with: name, industry, employeeCount, headquarters, description.',
        },
      ],
      responseFormat: 'json',
      outputSchema: {
        name: 'string',
        industry: 'string',
        employeeCount: 'string',
        headquarters: 'string',
        description: 'string',
      },
      retry: { maxAttempts: 2, backoffMs: 2000, backoffMultiplier: 2 },
    },
    {
      type: 'llm',
      name: 'draft-summary',
      model: 'claude-sonnet-4-20250514',
      systemPrompt:
        'You are a concise business writer. Write a two-sentence company summary from the research data.',
      messages: [
        {
          role: 'user',
          content:
            'Company: {{state.name}}\nIndustry: {{state.industry}}\nEmployees: {{state.employeeCount}}\nHQ: {{state.headquarters}}\nDescription: {{state.description}}\n\nReturn JSON: {"summary": "..."}',
        },
      ],
      responseFormat: 'json',
      outputSchema: { summary: 'string' },
      retry: { maxAttempts: 2, backoffMs: 1000, backoffMultiplier: 2 },
    },
    {
      type: 'task',
      name: 'finalize',
      handler: 'finalize-research',
    },
  ],
});

console.log(`Definition created: ${definition.id}`);

Step names must be unique within a definition. The {{input.*}} and {{state.*}} placeholders are resolved at runtime -- input refers to the data you pass when starting a run, and state accumulates output from previous steps.

Start a workflow run

Now start a run against the definition you just created. Pass any input data the workflow needs.

run.ts
const run = await runtime.workflows.create({
  definitionId: definition.id,
  input: {
    company: 'Anthropic',
  },
});

console.log(`Run started: ${run.id}`);
console.log(`Status: ${run.status}`);

The create() method accepts a CreateRunInput object:

FieldTypeRequiredDescription
definitionIdstringYesID of the workflow definition to run
inputRecord<string, unknown>NoInput data available as {{input.*}}
idempotencyKeystringNoPrevents duplicate runs for the same key

The returned WorkflowRun object includes fields like id, status, currentStepName, state, output, and totalCostCents.

Wait for completion

The SDK provides a built-in waitForCompletion() helper that polls the run status until it reaches a terminal state (COMPLETED, FAILED, or CANCELLED).

run.ts
const completed = await runtime.workflows.waitForCompletion(run.id, {
  pollIntervalMs: 2000,  // check every 2 seconds (default)
  timeoutMs: 60000,      // give up after 60 seconds
});

console.log(`Final status: ${completed.status}`);
console.log(`Output:`, completed.output);

If you prefer manual polling, use runtime.workflows.get() in a loop instead:

let current = await runtime.workflows.get(run.id);
while (current.status !== 'COMPLETED' && current.status !== 'FAILED') {
  await new Promise((r) => setTimeout(r, 3000));
  current = await runtime.workflows.get(run.id);
  console.log(`  ${current.status} — step: ${current.currentStepName}`);
}

Check cost

Every LLM call is tracked. Use getCost() to see the total spend and per-call breakdown for a run.

run.ts
const cost = await runtime.workflows.getCost(run.id);

console.log(`Total cost: $${cost.totalCostDollars}`);
console.log(`LLM calls: ${cost.calls.length}`);

for (const call of cost.calls) {
  console.log(
    `  ${call.model} — ${call.inputTokens} in / ${call.outputTokens} out — $${(call.costCents! / 100).toFixed(4)}`
  );
}

The RunCost object returned by getCost() has this shape:

FieldTypeDescription
totalCostCentsnumberTotal cost in cents across all LLM calls
totalCostDollarsstringFormatted dollar amount (e.g. "0.0042")
callsLlmCall[]Individual LLM call records with token counts

Complete example

Here is the full script assembled into a single runnable file:

run.ts
import { AgentRuntime } from '@stevora/sdk';

async function main() {
  // 1. Initialize the client
  const runtime = new AgentRuntime({
    apiKey: process.env.STEVORA_API_KEY!,
    baseUrl: 'http://localhost:3000',
  });

  // 2. Create a workflow definition
  const definition = await runtime.definitions.create({
    name: 'company-research',
    version: '1',
    description: 'Research a company and draft a summary',
    steps: [
      {
        type: 'llm',
        name: 'research-company',
        model: 'claude-sonnet-4-20250514',
        systemPrompt:
          'You are a business research assistant. Given a company name, return a JSON object with key facts.',
        messages: [
          {
            role: 'user',
            content:
              'Research this company: {{input.company}}. Return JSON with: name, industry, employeeCount, headquarters, description.',
          },
        ],
        responseFormat: 'json',
        outputSchema: {
          name: 'string',
          industry: 'string',
          employeeCount: 'string',
          headquarters: 'string',
          description: 'string',
        },
        retry: { maxAttempts: 2, backoffMs: 2000, backoffMultiplier: 2 },
      },
      {
        type: 'llm',
        name: 'draft-summary',
        model: 'claude-sonnet-4-20250514',
        systemPrompt:
          'You are a concise business writer. Write a two-sentence company summary from the research data.',
        messages: [
          {
            role: 'user',
            content:
              'Company: {{state.name}}\nIndustry: {{state.industry}}\nEmployees: {{state.employeeCount}}\nHQ: {{state.headquarters}}\nDescription: {{state.description}}\n\nReturn JSON: {"summary": "..."}',
          },
        ],
        responseFormat: 'json',
        outputSchema: { summary: 'string' },
        retry: { maxAttempts: 2, backoffMs: 1000, backoffMultiplier: 2 },
      },
      {
        type: 'task',
        name: 'finalize',
        handler: 'finalize-research',
      },
    ],
  });

  console.log(`Definition created: ${definition.id}`);

  // 3. Start a workflow run
  const run = await runtime.workflows.create({
    definitionId: definition.id,
    input: { company: 'Anthropic' },
  });

  console.log(`Run started: ${run.id} — status: ${run.status}`);

  // 4. Wait for completion
  const completed = await runtime.workflows.waitForCompletion(run.id, {
    pollIntervalMs: 2000,
    timeoutMs: 60000,
  });

  console.log(`Final status: ${completed.status}`);
  console.log('Output:', completed.output);

  // 5. Check cost
  const cost = await runtime.workflows.getCost(run.id);
  console.log(`Total cost: $${cost.totalCostDollars}`);

  for (const call of cost.calls) {
    console.log(
      `  ${call.model} — ${call.inputTokens} in / ${call.outputTokens} out — $${(call.costCents! / 100).toFixed(4)}`
    );
  }
}

main().catch(console.error);

Run it with:

STEVORA_API_KEY=stv_k1_your_key npx tsx run.ts

Next steps

  • Core Concepts -- Learn about workflow state, step types, and execution semantics
  • Step Types -- Explore all six step types: Task, Wait, Condition, External Event, LLM, and Approval
  • LLM Integration -- Configure models, tool calling, guardrails, and structured outputs
  • API Reference -- Full REST API documentation
  • Examples -- See a complete AI SDR outreach workflow