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/sdkInitialize 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.
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:
| Parameter | Type | Required | Description |
|---|---|---|---|
apiKey | string | Yes | Your workspace API key |
baseUrl | string | No | Stevora instance URL (defaults to localhost:3000) |
timeout | number | No | Request 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.
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.
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:
| Field | Type | Required | Description |
|---|---|---|---|
definitionId | string | Yes | ID of the workflow definition to run |
input | Record<string, unknown> | No | Input data available as {{input.*}} |
idempotencyKey | string | No | Prevents 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).
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.
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:
| Field | Type | Description |
|---|---|---|
totalCostCents | number | Total cost in cents across all LLM calls |
totalCostDollars | string | Formatted dollar amount (e.g. "0.0042") |
calls | LlmCall[] | Individual LLM call records with token counts |
Complete example
Here is the full script assembled into a single runnable file:
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.tsNext 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