---
title: Workflows
description: Start a Run of a Workflow that calls your HTTPS handler step by step, with retries and delayed starts, and read it back.
type: tutorial
product: workflows
summary: Declare a Workflow, start a Run, and read it back, with a handler of your own.
updated: 2026-10-05
order: 0
---

A Workflow names an HTTPS handler that your own service exposes. A Run is one
execution of it: Sylphx calls the handler with a signed request, records what
it answers, and retries it with backoff until the Run closes. A Run survives
deploys and restarts, and a delayed start is a durable timer.

<Prerequisites
	items={[
		'An account, and a project with an environment — see the platform start page',
		'An Access key with the workflows:write and workflows:run scopes (workflows:read to read Runs back)',
		'An https endpoint of yours on port 443 that answers a POST with a 2xx',
	]}
/>

Everything is under
`https://api.sylphx.com/v1/orgs/{org}/projects/{project}/envs/{env}/`, and an
environment outside the key's scope answers `404`.

## 1. Declare the Workflow

```bash
curl -X POST "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/workflows?workflow_id=hire-screening" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"spec":{"handlerUri":"https://app.example.com/api/sylphx/workflows","retryPolicy":{"maxAttempts":5},"timeouts":{"invocationTimeout":"120s"}}}'
```

The handler must be `https://` on port 443 and resolve to a public address.
By default a call is retried up to 10 times with a 1 second backoff that
doubles to 600 seconds, and a Run may last 30 days. A Run pins the handler it
started with, so later edits affect new Runs only.

## 2. Start a Run

```bash
curl -X POST "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/workflows/hire-screening/runs?run_id=applicant-8f2c" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"input":{"applicantId":"8f2c"},"startDelay":"900s"}'
```

```ts
const run = await sylphx.workflows.runs.create({
  parent: 'orgs/acme/projects/shop/envs/production/workflows/hire-screening',
  runId: 'applicant-8f2c',
  run: { input: { applicantId: '8f2c' }, startDelay: '900s' },
})
```

`run_id` is your business key. Starting the same `run_id` with the same input
returns the existing Run with `200` and `Sylphx-Idempotent-Replay: true`; a
different input under the same id is `409`. `startDelay` (at most 365 days)
delays the first call to your handler.

## 3. Answer the handler

Your handler receives a POST signed with a key you verify against
`https://api.sylphx.com/.well-known/workflows-jwks.json`. A plain `2xx` answer
completes the Run, with a JSON body as its output, so a handler that just does
the work needs nothing more. A `400`, `401`, `403`, `404`, `410` or `422` fails
the Run with no retry. A `408`, `409`, `425`, `429`, a `5xx` or a dropped
connection is retried under the Workflow's retry policy.

## 4. Read the Run back

```bash
curl "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/workflows/hire-screening/runs/applicant-8f2c" \
  -H "Authorization: Bearer $SYLPHX_API_KEY"
```

`state` is `running`, `succeeded`, `failed`, `cancelled` or `timed_out`. To
block until it closes, call `POST …/runs/{run}:wait` with
`{"timeout":"30s"}`; to stop it, `POST …/runs/{run}:cancel`. A Run is
metered as `workflows.actions`: one for its start and one for each attempt
that reached your handler.

- [The workflows collection](/docs/api/workflows)
- [The runs collection](/docs/api/runs)
