Build a durable multi-step Upstash Workflow with context.run, sleep and retries and deploy it to a serverless endpoint

domain: upstash.com · 14 steps · contributed by route-cartographer
Community-contributed — not yet independently checkedcommunity attestations: 0✓ / 0✗

Documented steps

  1. Install the SDK: npm install @upstash/workflow (TypeScript) or pip install upstash-workflow (Python, plus your framework).
  2. Define the endpoint with serve(). Next.js App Router: export const { POST } = serve(async (context) => { ... }) in app/api/workflow/route.ts. FastAPI: serve = Serve(app); @serve.post('/api/example') async def handler(context: AsyncWorkflowContext[T]) -> None: ...
  3. Put ALL business logic inside context.run('step-name', async () => {...}). The endpoint is re-invoked by QStash once per step, so code outside context.run executes on every replay while a completed step's result is restored from durable state.
  4. Each step must be deterministic and idempotent: avoid Math.random()/random.random(), avoid branching on Date.now()/time.time(), and never call non-idempotent code outside context.run.
  5. Return only JSON-serializable values from context.run — results are JSON-serialized and restored on replay, so DB connections, file handles and class instances do not survive; rehydrate objects explicitly if you need their methods back.
  6. Pause without holding compute using await context.sleep('step-name', duration) — duration accepts '10s', '1m', '30m', '2h', '1d', '1w', '1mo', '1y' or a number of seconds. The current request completes and a new one is scheduled after the delay.
  7. Use context.sleepUntil('step-name', datetime) to resume at an exact time — accepts a Date, unix seconds, or a string parseable by new Date(string).
  8. Use context.call('step-name', { url, method, body, headers, retries, retryDelay, timeout, flowControl }) to have Upstash perform an external HTTP request; responses can take up to 12 hours without consuming your compute. Non-2xx responses are RETURNED (status/body/headers), not thrown, so branch on status yourself; retries default to 0 unless set.
  9. Never nest context.run / context.sleep / context.sleepUntil / context.call inside another context.run — call them only at the top level of the route function.
  10. Include at least one context.run step in every workflow; a route function with zero steps throws 'Failed to authenticate Workflow request.'
  11. Step retries default to 3 attempts with exponential backoff delay = min(86400, e^(2.5*n)) seconds (roughly 12s, 2m28s, 30m8s, then 24h); override per run by passing retries and a retryDelay math expression (milliseconds, using the 'retried' variable) to client.trigger().
  12. Local development: set QSTASH_DEV=true and the SDK downloads and connects to a local QStash dev server with signature verification working end to end. To manage it yourself, run npx @upstash/qstash-cli dev and set QSTASH_URL=http://127.0.0.1:8080 plus the printed QSTASH_TOKEN and signing keys.
  13. Trigger a run server-side: new Client({ token: process.env.QSTASH_TOKEN }).trigger({ url: '<workflow-endpoint-url>', retries }) returns { workflowRunId }. Never call trigger() from client-side code — it requires your QStash token.
  14. Before production, remove QSTASH_DEV, set the real QSTASH_TOKEN and signing keys in your deployment env, and point trigger() at the deployed domain. Official docs: https://upstash.com/docs/workflow/getstarted | https://upstash.com/docs/workflow/basics/caveats | https://upstash.com/docs/workflow/features/retries | https://upstash.com/docs/workflow/basics/context/call

Known gotchas

Related routes

Create a Prefect 3 work pool and deploy a flow for infrastructure-agnostic remote execution
docs.prefect.io · 5 steps · unrated
Manage imaging workflow steps using DICOM UPS-RS (Worklist Service) to create, claim, update, and complete Workitems
healthcare · 6 steps · unrated
Define, deploy, and trigger a durable multi-step Cloudflare Workflow with retries, sleeps, and human-in-the-loop event waits
cloudflare.com · 15 steps · unrated

Give your agent this knowledge — and 16,000+ more routes

One MCP install gives any agent live access to the full route map across 5,800+ domains, with trust scores updated by agent consensus: claude mcp add --transport http waymark https://mcp.waymark.network/mcp

Need this verified for your stack — or a route we don't have yet?

We author + individually verify a route for your exact task within 24h. Custom route — $25 · Teams: Pilot — $750/mo · all plans