Quickstart

Get your first durable run finishing in five steps: create a project, issue a key, write a workflow, connect a runner, and trigger it.

Five steps: create a project, issue a key, write a workflow, connect a runner, trigger it.

Duraton is in beta. Install from the @next tag: @duraton/sdk@next. A plain npm install @duraton/sdk resolves to the latest tag, which currently lags behind and is missing the recent features (run tags, bulkCancel, watchFiltered, step.skip, per-step retry, and more)

  • with no error to tell you. Use @next until latest catches up.

1. Create your project

Sign up at app.duraton.dev. A workspace is created for you with a first project inside it, and the Overview walks you through the steps below. A project is the isolated slice - its own runs, events, keys, and runners; a workspace holds many. You can add more projects - and invite members - later.

2. Issue an API key

Keys are scoped to a project. In the console, open API Keys and issue a secret key. A secret key is read + write - your runner connects and reports results with it. It's shown once, so copy it now.

Issuing an API key in the console

Set it in your runner's environment:

export DURATON_API_KEY="dtn_live_..."

3. Write a workflow

Add the SDK for your language. The TypeScript SDK runs on Node.js and Bun:

npm install @duraton/sdk@next

A workflow is a function triggered by an event. Wrap each unit of work in a step so it runs once and its result is remembered.

runner.ts
import { defineWorkflow } from "@duraton/sdk";

interface TicketData {
  ticketId: string;
}

const ticketCreated = defineWorkflow<TicketData>({
  name: "ticket.created",
  retry: { maxAttempts: 3 },
  handler: async (ctx) => {
    const { ticketId } = ctx.event.data;

    const triage = await ctx.step.run("triage", () => ({
      category: "billing",
      priority: "high",
    }));

    await ctx.step.sleep("cool-off", "10s");

    const refund = await ctx.step.run("refund", () => ({
      refundId: `re_${ticketId}`,
    }));

    return { ticketId, triage, refund };
  },
});

4. Connect a runner

connect() dials Duraton over an outbound WebSocket. There is no port to expose, no public URL, and no separate registration - it works from a laptop, a container, or a box behind NAT. It reads DURATON_API_KEY from the environment you set in step 2. app groups a runner's workflows under a name the console and other runners see; skip it and it defaults to "default".

runner.ts
import { connect } from "@duraton/sdk";

connect({ app: "support-app", workflows: [ticketCreated] });
npx tsx runner.ts

Open Apps in the console: the runner is listed as connected, with its workflows.

Connected apps and their runners in the console

Already hosting a public HTTP service?

If your workflows live in a service you already expose at a public address - a Next.js app, an Express API, a serverless function - mount them there with serve() instead. Duraton POSTs each invoke to runnerUrl, so that address must be reachable from the internet.

Each adapter both serves your workflows and registers them: passing app turns on registration, and the Duraton URL comes from DURATON_URL. The route defaults to /invoke.

A serve() runner must be reachable at its runnerUrl. If it is not - private network, laptop, NAT, or a dynamic address - use connect().

app/api/duraton/route.ts
import { serve } from "@duraton/sdk/next";
import { ticketCreated } from "../../../workflows";

export const { POST } = serve({
  app: "support-app",
  runnerUrl: "https://your-app.example.com/api/duraton",
  workflows: [ticketCreated],
});

Next.js and Bun need only the SDK. The others also need their framework installed - add hono, elysia, express, or fastify with your package manager.

5. Trigger it

Send the event the workflow listens for - with the SDK client, or over the REST API authenticated with your key:

import { createClient } from "@duraton/sdk";

const duraton = createClient({
  url: process.env.DURATON_URL!,
  apiKey: process.env.DURATON_API_KEY,
});

await duraton.events.send({
  name: "ticket.created",
  app: "support-app",
  data: { ticketId: "T-421" },
});

Open Runs in the console. triage completes, the run waits out the sleep, then refund completes and the run succeeds - streaming in live as it executes.

Runs in the console

6. Restart the runner mid-run

While the run is sleeping, stop your runner (Ctrl-C) and start it again the same way you ran it above. Duraton holds the run and re-invokes when the runner reconnects. triage does not run a second time - its result was recorded on the first pass, so the run resumes at refund.

Next steps

On this page