# Workflows SDK for Python — Usage and symbol reference

Current version: [Python SDK](https://github.com/render-oss/sdk/releases) 1.x

Other versions: [0.x](/docs/workflows-sdk-python/v0.md)

The Render SDK for Python provides support for:

- Defining tasks and flows in your workflow service
- Running functions as steps inside a flow
- Chaining additional task runs from within a task function
- Triggering and managing runs from Python code outside the workflow

## Install

From your Python project directory:

```shell
pip install render
```

*If you already have the SDK installed,* make sure you're using version `1.7.0` or later:

```shell
pip install --upgrade render
```

After installing, make sure to add `render>=1.7.0` as a dependency in your application's `requirements.txt`, `pyproject.toml`, or equivalent.

## Workflow setup

The following symbols pertain to setting up your workflow service. For usage guidance, see [Defining Tasks](/workflows-defining-tasks) and [Defining Flows](/workflows-defining-flows).

### The `Workflows` class

Handles defining and registering workflow tasks and flows. Initialize this in each file where you define tasks or flows.

#### Constructor

###### `Workflows(*, default_retry=None, default_timeout=None, default_plan=None)`

Initializes a new `Workflows` object. All parameters are optional.

```python
from render import Workflows, Retry

# Basic initialization
app = Workflows()

# Initialization with all options set
app = Workflows(
  default_retry=Retry(
    max_retries=3,
    wait_duration_ms=1000,
    backoff_scaling=1.5
  ),
  default_timeout=300,
  default_plan="flex"
)
```

------

##### `default_retry`

A `Retry` object defining default retry behavior for all tasks registered directly on this `Workflows` object. See [Retry settings](#max-retries).

This setting does not apply to flows.

Individual tasks can [override this](/workflows-defining-tasks#retry-settings). If neither the workflow nor the task specifies retry settings, Render applies [default retry behavior](/workflows-defining-tasks#default-retry-behavior).

##### `default_timeout`

The default timeout for each execution attempt (in seconds) for all tasks and flows registered directly on this `Workflows` object.

Individual definitions can override this with `timeout_seconds`.

##### `default_plan`

The default compute plan to use for all tasks and flows registered directly on this `Workflows` object.

Individual definitions can override this with `plan`.

One of the following:

| CPU | RAM | Plan ID |
| --- | --- | --- |
| _Up to_ 1 CPU | _Up to_ 4 GB | [*`flex`*](/workflows-limits#the-flex-compute-plan) |
| 2 | 4 GB | *`2c-4g`* |
| 2 | 8 GB | *`2c-8g`* |
| 4 | 8 GB | *`4c-8g`* |
| 4 | 16 GB | *`4c-16g`* |
| 8 | 32 GB | *`8c-32g`* |

The default value is `flex`.

Providing `starter` or `standard` is equivalent to providing `flex`.

------

###### `Workflows.from_workflows(*apps, default_retry=None, default_timeout=None, default_plan=None)`

Initializes a new `Workflows` object that incorporates tasks and flows from one or more _other_ `Workflows` objects.

Use this in your workflow's entry point file if you import [task definitions](/workflows-defining-tasks#organizing-definitions) or [flow definitions](/workflows-defining-flows#organizing-definitions) from other files:

```python
from render import Workflows
from math_tasks import app as math_app
from text_tasks import app as text_app

app = Workflows.from_workflows(math_app, text_app)

if __name__ == "__main__":
  app.start()
```

If you set any defaults with this method (such as `default_timeout`), those defaults apply _only_ to definitions registered directly on _this_ `Workflows` object (not to definitions registered on the imported objects). `default_retry` applies only to tasks.

| Parameter | Description |
| --- | --- |
| `*apps` | *Required.* One or more `Workflows` objects to incorporate into this object. Task and flow names share a namespace. If two objects define a task or flow with the same name, this raises a `ValueError`. |
| `default_retry` | The default retry configuration for new tasks registered on this workflow. Same as the [`Workflows` constructor](#workflows) parameter. |
| `default_timeout` | The default timeout for new tasks and flows registered on this workflow. Same as the [`Workflows` constructor](#workflows) parameter. |
| `default_plan` | The default compute plan for new tasks and flows registered on this workflow. Same as the [`Workflows` constructor](#workflows) parameter. |

### The `app.start()` method

###### `Workflows.start()`

The `app.start()` method serves as the entry point for your workflow during both task and flow registration and run execution. Your workflow definition must call this method as part of startup:

```python
from render import TaskContext, Workflows

app = Workflows()

@app.task
def calculate_square(ctx: TaskContext, a: int) -> int:
  return a * a

if __name__ == "__main__":
  app.start() # highlight-line
```

This method has no parameters.

## Defining tasks

The following symbols pertain to creating and registering tasks in your workflow service. For usage guidance, see [Defining Tasks](/workflows-defining-tasks).

### The `@app.task` decorator

###### `@app.task(*, name=None, retry=None, timeout_seconds=None, plan=None)`

You apply the `@app.task` decorator to a Python function to register it as a workflow task. For details, see [Defining Tasks](/workflows-defining-tasks).

*On success:* Returns a task definition that you can pass to [`ctx.run()`](#async-taskcontext-run) to chain a run of the task.

*Basic usage:*

```python
from render import TaskContext, Workflows # highlight-line

app = Workflows() # highlight-line

@app.task # highlight-line
def calculate_square(ctx: TaskContext, a: int) -> int: # highlight-line
  return a * a
```

*With all configuration options:*

```python
from render import Retry, TaskContext, Workflows

app = Workflows()

@app.task(
  name="calc_square", # Give the task a custom name (defaults to function name)
  retry=Retry( # Define default retry logic for the task
    max_retries=3, # Retry up to 3 times (i.e., 4 total attempts)
    wait_duration_ms=1000, # Set a base retry delay of 1 second
    backoff_scaling=1.5 # Increase delay by 50% after each retry (exponential backoff)
  ),
  timeout_seconds=300, # Timeout in seconds
  plan="2c-4g" # Compute plan
)
def calculate_square(ctx: TaskContext, a: int) -> int:
  return a * a
```

------

###### *Decorator parameters*

---

###### Parameter

`name`

###### Description

A custom name for the task.

This affects the task's *slug*, which you use to reference the task when [triggering runs](/workflows-running#4-trigger-a-task-run).

Use a name that is unique across all tasks and flows in the workflow.

If omitted, defaults to the name of the decorated function.

---

###### Parameter

`retry`

###### Description

A `Retry` object defining retry behavior for the task. See all supported settings below.

If omitted, uses the workflow's `default_retry` setting. If neither is provided, Render applies [default retry behavior](/workflows-defining-tasks#default-retry-behavior).

---

###### Parameter

`timeout_seconds`

###### Description

The timeout for each execution attempt, in seconds.

Must be between 30 seconds (`30`) and 24 hours (`86400`), inclusive.

If omitted, uses the workflow's `default_timeout` setting. If neither is provided, the default is 2 hours.

---

###### Parameter

`plan`

###### Description

The compute plan to use for the task's runs.

One of the following:

| CPU | RAM | Plan ID |
| --- | --- | --- |
| _Up to_ 1 CPU | _Up to_ 4 GB | [*`flex`*](/workflows-limits#the-flex-compute-plan) |
| 2 | 4 GB | *`2c-4g`* |
| 2 | 8 GB | *`2c-8g`* |
| 4 | 8 GB | *`4c-8g`* |
| 4 | 16 GB | *`4c-16g`* |
| 8 | 32 GB | *`8c-32g`* |

If omitted, uses the workflow's `default_plan` setting. If neither is provided, the default is `flex`.

Providing `starter` or `standard` is equivalent to providing `flex`.

---

###### *Retry settings*

---

###### Parameter

`max_retries`

###### Description

*Required* if you provide [`retry`](#retry). The maximum number of retries to attempt for a given run of the task. The total number of attempts is up to `max_retries + 1` (the initial attempt plus all retries).

---

###### Parameter

`wait_duration_ms`

###### Description

*Required* if you provide [`retry`](#retry). The base delay before attempting the first retry, in milliseconds.

---

###### Parameter

`backoff_scaling`

###### Description

The exponential backoff factor. After each retry, the previous delay is multiplied by this factor.

For example, a factor of `1.5` increases the delay by 50% after each retry.

- If you provide a `Retry` object but omit this field, the default value is `1.5`.
- If neither the task nor the workflow provides a `Retry` object, the default value is `2`.

------

### The `TaskContext` object

Every task function must accept a `TaskContext` object as its first positional argument. Render automatically provides this object to each run (you do not provide it as an input).

Use [`run()`](#async-taskcontext-run) for run chaining. Read [`metadata`](#taskcontext-metadata) to get the current run's IDs.

###### `async TaskContext.run(task, *args, *kwargs)`

Chains a run of the specified task on a separate instance.

*On success:* Returns the chained run's return value.

*Raises:* [`TaskRunError`](#taskrunerror), `ValueError`

```python
import asyncio

@app.task
async def sum_squares(ctx: TaskContext, a: int, b: int) -> int:
  first, second = await asyncio.gather(
    ctx.run(calculate_square, a), # highlight-line
    ctx.run(calculate_square, b) # highlight-line
  )
  return first + second
```

------

##### `task`

*Required.** The `@app.task`-decorated function to run, such as `calculate_square` in the example above.

##### `*args`

All [input arguments](/workflows-defining-tasks#function-arguments) to pass to the task run, as _positional_ arguments:

```python
await ctx.run(sum_squares, 2, 3)
```

You cannot provide both positional and named task arguments in the same call (this raises a `ValueError`).

##### `**kwargs`

All [input arguments](/workflows-defining-tasks#function-arguments) to pass to the task run, as _named_ arguments:

```python
await ctx.run(sum_squares, a=2, b=3)
```

You cannot provide both positional and named task arguments in the same call (this raises a `ValueError`).

------

###### `TaskContext.metadata`

An object containing identifiers for the current task run and its execution chain.

Added in SDK v1.2.0.

| Property | Description |
| --- | --- |
| `task_run_id` | The ID of this task run. |
| `root_task_run_id` | The ID of the root-level task run for this run's execution chain. If this run _is_ the root-level run, this value is the same as `task_run_id`. |
| `parent_task_run_id` | The ID of the task run that chained this run. This value is `None` for a root-level run. |

## Defining flows

The following symbols pertain to creating and registering flows in your workflow service. For usage guidance, see [Defining Flows](/workflows-defining-flows).

### The `@app.flow` decorator

###### `@app.flow(*, name=None, timeout_seconds=None, plan=None)`

You apply the `@app.flow` decorator to a Python function to register it as a flow. For details, see [Defining Flows](/workflows-defining-flows#function-arguments).

*On success:* Returns a flow definition.

*Basic usage:*

```python
from render import FlowContext, Workflows

app = Workflows()

@app.flow
def format_message(ctx: FlowContext, message: str) -> str:
  trimmed = ctx.step("trim", lambda text: text.strip(), message)
  uppercase = ctx.step("uppercase", lambda text: text.upper(), trimmed)
  return uppercase
```

*With all configuration options:*

```python
from render import FlowContext, Workflows

app = Workflows()

@app.flow(
  name="format_message",
  timeout_seconds=300,
  plan="flex"
)
def format_message(ctx: FlowContext, message: str) -> str:
  trimmed = ctx.step("trim", lambda text: text.strip(), message)
  uppercase = ctx.step("uppercase", lambda text: text.upper(), trimmed)
  return uppercase
```

------

###### *Decorator parameters*

---

###### Parameter

`name`

###### Description

A custom name for the flow.

This affects the flow's *slug*, which you use to reference the flow when [triggering runs](#start-flow).

Use a name that is unique across all tasks and flows in the workflow.

If omitted, defaults to the name of the decorated function.

---

###### Parameter

`timeout_seconds`

###### Description

The timeout for each execution attempt, in seconds.

Must be between 30 seconds (`30`) and 24 hours (`86400`), inclusive.

If omitted, uses the workflow's `default_timeout` setting. If neither is provided, the default is 2 hours.

---

###### Parameter

`plan`

###### Description

The compute plan to use for the flow's runs.

One of the following:

| CPU | RAM | Plan ID |
| --- | --- | --- |
| _Up to_ 1 CPU | _Up to_ 4 GB | [*`flex`*](/workflows-limits#the-flex-compute-plan) |
| 2 | 4 GB | *`2c-4g`* |
| 2 | 8 GB | *`2c-8g`* |
| 4 | 8 GB | *`4c-8g`* |
| 4 | 16 GB | *`4c-16g`* |
| 8 | 32 GB | *`8c-32g`* |

If omitted, uses the workflow's `default_plan` setting. If neither is provided, the default is `flex`.

Providing `starter` or `standard` is equivalent to providing `flex`.

------

### The `FlowContext` object

Every flow function receives a `FlowContext` as its first positional argument. Render provides this object automatically.

For usage guidance, see [Configuring step behavior](/workflows-defining-flows#configuring-step-behavior).

###### `FlowContext.step(name, fn, /, *args, retry=None)`

Runs a plain function as a step inside the flow's process.

*On success:* Returns the function's return value.

*Raises:* [`TerminalFlowError`](#terminalflowerror), [`DeterminismError`](#determinismerror), [`FlowBadRequestError`](#flowbadrequesterror), [`FlowLimitError`](#flowlimiterror), [`ExecutionError`](#executionerror), `TypeError`, `ValueError`

```python
@app.flow
def process_order(ctx: FlowContext, order_id: str):
  receipt = ctx.step( # highlight-line
    "charge_card", charge_card, order_id, retry=Retry(max_retries=3, wait_duration_ms=500) # highlight-line
  ) # highlight-line
  return receipt
```

------

###### *Function parameters*

---

###### Parameter

`name`

###### Description

*Required.* The step's name.

This value is not required to match the name of the function you provide as `fn`.

---

###### Parameter

`fn`

###### Description

*Required.* The function to execute as a step. The function must be synchronous.

The function's arguments and return value must be JSON-serializable.

You must pass a plain function. You _cannot_ pass a definition returned by [`@app.task`](#app-task) or [`@app.flow`](#app-flow).

---

###### Parameter

`*args`

###### Description

*Required* if the function takes arguments. The positional argument values to pass to `fn`.

For functions that take zero arguments, omit these arguments.

You cannot pass the function's arguments by keyword.

---

###### Parameter

`retry`

###### Description

A `Retry` object defining retry behavior for the step. See all supported settings below.

If a step's return value fails JSON serialization, the step fails without retrying.

If all attempts fail, `ctx.step()` raises [`TerminalFlowError`](#terminalflowerror). The entire flow run fails even if this error is caught.

If you omit this field, the step is attempted once.

---

###### *Retry settings*

---

###### Parameter

`max_retries`

###### Description

*Required* if you provide [`retry`](#retry-1). The maximum number of retries to attempt for a given step. The total number of attempts is up to `max_retries + 1` (the initial attempt plus all retries).

---

###### Parameter

`wait_duration_ms`

###### Description

*Required* if you provide [`retry`](#retry-1). The base delay before attempting the first retry, in milliseconds.

---

###### Parameter

`backoff_scaling`

###### Description

The exponential backoff factor. After each retry, the previous delay is multiplied by this factor.

For example, a factor of `1.5` increases the delay by 50% after each retry.

The default value is `1.5`.

------

###### `FlowContext.metadata`

An object containing the current flow run's ID.

| Property | Description |
| --- | --- |
| `flow_run_id` | The ID of this flow run. Has the format `frn-abc123...`. |

## Triggering runs

The following symbols pertain to triggering and managing task and flow runs from code outside your workflow. For full usage guidance, see [Triggering Runs](/workflows-running).

> When the Render API omits an optional field on a returned object, the corresponding Python property contains `UNSET`, imported from `render.public_api.types`. This is distinct from `None`, which represents a JSON `null` value.

### Initialize the client (sync or async)

Use `Render` for synchronous execution contexts (such as a default Flask or Django app), or `RenderAsync` for asynchronous contexts (such as FastAPI route handlers). Both clients support all methods under [Task methods](#task-methods) and [Flow methods](#flow-methods).

With `RenderAsync`, you `await` method calls and use `async for` to consume event streams. The examples below assume you are already in an asynchronous execution context when using this client.

**Tab: Sync client**

###### `Render(*, token=None)`

Initializes a synchronous Render SDK client.

```python
from render import Render

# Uses RENDER_API_KEY from the environment
render = Render()

# Provide an API key explicitly
render_with_token = Render(token="rnd_abc123…")
```

| Parameter | Description |
| --- | --- |
| `token` | The [API key](/api#1-create-an-api-key) to use for authentication. If omitted, the client automatically detects and uses the value of the `RENDER_API_KEY` environment variable. |

**Tab: Async client**

###### `RenderAsync(*, token=None)`

Initializes an asynchronous Render SDK client.

```python
from render import RenderAsync

# Uses RENDER_API_KEY from the environment
render = RenderAsync()

# Provide an API key explicitly
render_with_token = RenderAsync(token="rnd_abc123…")
```

| Parameter | Description |
| --- | --- |
| `token` | The [API key](/api#1-create-an-api-key) to use for authentication. If omitted, the client automatically detects and uses the value of the `RENDER_API_KEY` environment variable. |

### Task methods

These methods are available on the `workflows` attribute of either client. Unless indicated otherwise, parameter descriptions and returned types apply to both clients.

###### `start_task(task_slug, input_data, *, idempotency_key=None)`

Kicks off a run of the registered task with the specified identifier, passing the specified arguments.

To instead start a task _and_ wait for it to finish, use [`workflows.run_task`](#run-task).

*On success:* Returns a [`TaskRun`](#taskrun) with the sync client, or an [`AwaitableTaskRun`](#awaitabletaskrun) with the async client. Both provide the run's ID and initial status immediately. You can `await` an `AwaitableTaskRun` to wait for completion and obtain [`TaskRunDetails`](#taskrundetails).

*Raises:* [`ClientError`](#clienterror), [`RateLimitError`](#ratelimiterror), [`ServerError`](#servererror), [`TimeoutError`](#timeouterror)

**Tab: Sync client**

```python
started_run = render.workflows.start_task(
  "my-workflow/calculate_square",
  [2],
  idempotency_key="calculate-square-request-123"
)

print(started_run.id) # Available immediately

# Use run_task() instead to start a run and block until it finishes.
```

**Tab: Async client**

```python
started_run = await render.workflows.start_task(
  "my-workflow/calculate_square",
  [2],
  idempotency_key="calculate-square-request-123"
)

print(started_run.id) # Available immediately

finished_run = await started_run
print(finished_run.results) # [4]
```

------

##### `task_slug`

*Required.* The *slug* indicating the task to run, available from your task's page in the [Render Dashboard](https://dashboard.render.com):

[image: Task slug in the Render Dashboard]

Always has the format `{workflow-slug}/{task-name}` (e.g., `my-workflow/calculate_square`).

##### `input_data`

*Required.* A list or dictionary containing the task's [input arguments](/workflows-defining-tasks#function-arguments).

- *If you provide a list,* elements are positional based on the task's function signature.
- *If you provide a dictionary,* each key maps to a parameter name in the task's function signature.

For a task that takes zero arguments, provide an empty list, `[]`.

##### `idempotency_key`

An optional string used to prevent duplicate task runs.

Added in SDK v1.2.0.

For details, see [Idempotency keys](/workflows-running#idempotency-keys).

------

###### `run_task(task_slug, input_data, *, idempotency_key=None)`

Starts the registered task with the specified identifier, waits for it to finish, and returns its details.

To instead start a run _without_ waiting for completion, use [`workflows.start_task`](#start-task).

*On success:* Returns a [`TaskRunDetails`](#taskrundetails) object.

*Raises:* [`ClientError`](#clienterror), [`RateLimitError`](#ratelimiterror), [`ServerError`](#servererror), [`TimeoutError`](#timeouterror), [`TaskRunError`](#taskrunerror), [`RenderError`](#rendererror)

**Tab: Sync client**

```python
task_run = render.workflows.run_task(
  "my-workflow/calculate_square",
  [2]
)
print(task_run.results) # [4]
```

**Tab: Async client**

```python
task_run = await render.workflows.run_task(
  "my-workflow/calculate_square",
  [2]
)
print(task_run.results) # [4]
```

------

##### `task_slug`

*Required.* The *slug* indicating the task to run, available from your task's page in the [Render Dashboard](https://dashboard.render.com):

[image: Task slug in the Render Dashboard]

Always has the format `{workflow-slug}/{task-name}` (e.g., `my-workflow/calculate_square`).

##### `input_data`

*Required.* A list or dictionary containing the task's [input arguments](/workflows-defining-tasks#function-arguments).

- *If you provide a list,* elements are positional based on the task's function signature.
- *If you provide a dictionary,* each key maps to a parameter name in the task's function signature.

For a task that takes zero arguments, provide an empty list, `[]`.

##### `idempotency_key`

An optional string used to prevent duplicate task runs.

Added in SDK v1.2.0.

For details, see [Idempotency keys](/workflows-running#idempotency-keys).

------

###### `list_task_runs(params=None)`

Lists task runs that match optional filters specified in a `ListTaskRunsParams` object.

*On success:* Returns a list of [`TaskRunWithCursor`](#taskrunwithcursor) objects.

*Raises:* [`ClientError`](#clienterror), [`RateLimitError`](#ratelimiterror), [`ServerError`](#servererror), [`TimeoutError`](#timeouterror)

**Tab: Sync client**

```python
from render.client.types import ListTaskRunsParams

params = ListTaskRunsParams(
  limit=10,
  owner_id=["tea-d3jm7ai4d50c73fale60"]
)

task_runs = render.workflows.list_task_runs(params)
for item in task_runs:
  print(item.task_run.id, item.cursor)
```

**Tab: Async client**

```python
from render.client.types import ListTaskRunsParams

params = ListTaskRunsParams(
  limit=10,
  owner_id=["tea-d3jm7ai4d50c73fale60"]
)

task_runs = await render.workflows.list_task_runs(params)
for item in task_runs:
  print(item.task_run.id, item.cursor)
```

The optional `params` object supports the following fields:

| Parameter | Description |
| --- | --- |
| `limit` | An integer specifying the maximum number of task runs to return. |
| `cursor` | A cursor string for pagination. Use this to retrieve the next page of results. |
| `owner_id` | A list of workspace IDs to filter results by. Only task runs from these workspaces will be returned. |

###### `get_task_run(task_run_id)`

Retrieves the details of the task run with the specified ID.

*On success:* Returns a [`TaskRunDetails`](#taskrundetails) object, regardless of whether the run is still in progress or has finished. A failed status does not itself raise `TaskRunError`.

*Raises:* [`ClientError`](#clienterror), [`RateLimitError`](#ratelimiterror), [`ServerError`](#servererror), [`TimeoutError`](#timeouterror)

**Tab: Sync client**

```python
render.workflows.get_task_run("trn-abc123")
```

**Tab: Async client**

```python
await render.workflows.get_task_run("trn-abc123")
```

| Parameter | Description |
| --- | --- |
| `task_run_id` | *Required.* The ID of the task run to retrieve. Has the format `trn-abc123...` |

###### `cancel_task_run(task_run_id)`

Cancels the root-level task run with the specified ID, along with all of its chained runs. Raises [`ClientError`](#clienterror) if the root-level run is not found.

Runs that have already finished are left unchanged.

*On success:* Returns `None`.

*Raises:* [`ClientError`](#clienterror), [`RateLimitError`](#ratelimiterror), [`ServerError`](#servererror), [`TimeoutError`](#timeouterror)

**Tab: Sync client**

```python
render.workflows.cancel_task_run("trn-abc123")
```

**Tab: Async client**

```python
await render.workflows.cancel_task_run("trn-abc123")
```

| Parameter | Description |
| --- | --- |
| `task_run_id` | *Required.* The ID of the root-level task run to cancel. Has the format `trn-abc123...` |

###### `task_run_events(task_run_ids)`

Streams terminal-status events for one or more task runs via [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) (SSE).

*On success:* Returns an iterator of [`TaskRunDetails`](#taskrundetails) objects: a regular iterator with `Render`, or an async iterator with `RenderAsync`. Each event describes a run that has reached a [terminal status](#run-status): `completed`, `failed`, or `canceled`. A failed run is yielded as an event (it does not raise `TaskRunError`).

The stream finishes when the server closes the connection after all requested runs have finished. You can also stop consuming events by breaking out of the loop.

*Raises:* [`ClientError`](#clienterror), [`RateLimitError`](#ratelimiterror), [`ServerError`](#servererror), [`TimeoutError`](#timeouterror)

**Tab: Sync client**

```python
run1 = render.workflows.start_task("my-workflow/add", [1, 2])
run2 = render.workflows.start_task("my-workflow/add", [3, 4])

for event in render.workflows.task_run_events([run1.id, run2.id]):
  print(f"Run {event.id}: status={event.status.value}")
  print(event.results)
```

**Tab: Async client**

```python
run1 = await render.workflows.start_task("my-workflow/add", [1, 2])
run2 = await render.workflows.start_task("my-workflow/add", [3, 4])

async for event in render.workflows.task_run_events([run1.id, run2.id]):
  print(f"Run {event.id}: status={event.status.value}")
  print(event.results)
```

| Parameter | Description |
| --- | --- |
| `task_run_ids` | *Required.* A list of task run IDs to stream events for. Each ID has the format `trn-abc123...` |

### Flow methods

These methods are available on the `workflows` attribute of either client. Unless indicated otherwise, parameter descriptions and returned types apply to both clients.

###### `start_flow(flow_slug, input_data, *, idempotency_key=None)`

Kicks off a run of the registered flow with the specified identifier, passing the specified arguments.

To instead run a flow _and_ wait for it to complete, use [`workflows.run_flow`](#run-flow).

*On success:* Returns a [`FlowRun`](#flowrun) object representing the started run.

*Raises:* [`ClientError`](#clienterror), [`RateLimitError`](#ratelimiterror), [`ServerError`](#servererror), [`TimeoutError`](#timeouterror)

**Tab: Sync client**

```python
started_run = render.workflows.start_flow(
  "my-workflow/format_message",
  ["hello"],
  idempotency_key="format-message-request-123"
)
print(started_run.id) # Available immediately
```

**Tab: Async client**

```python
started_run = await render.workflows.start_flow(
  "my-workflow/format_message",
  ["hello"],
  idempotency_key="format-message-request-123"
)
print(started_run.id) # Available immediately
```

------

##### `flow_slug`

*Required.* The *slug* indicating the flow to run, available from your flow's page in the [Render Dashboard](https://dashboard.render.com).

Always has the format `{workflow-slug}/{flow-name}` (e.g., `my-workflow/format_message`).

##### `input_data`

*Required.* A list or dictionary containing the flow's [input arguments](/workflows-defining-flows#function-arguments).

- *If you provide a list,* elements are positional based on the flow's function signature.
- *If you provide a dictionary,* each key maps to a parameter name in the flow's function signature.

For a flow that takes zero arguments, provide an empty list, `[]`.

##### `idempotency_key`

An optional string used to prevent duplicate flow runs.

For details, see [Idempotency keys](/workflows-running#idempotency-keys).

------

###### `run_flow(flow_slug, input_data, *, idempotency_key=None)`

Starts the registered flow with the specified identifier, waits for it to complete, and returns the result.

To instead kick off a run _without_ waiting for it to complete, use [`workflows.start_flow`](#start-flow).

*On success:* Returns a [`FlowRunDetails`](#flowrundetails) object for the finished flow run.

*Raises:* [`ClientError`](#clienterror), [`RateLimitError`](#ratelimiterror), [`ServerError`](#servererror), [`TimeoutError`](#timeouterror), [`FlowRunError`](#flowrunerror)

**Tab: Sync client**

```python
flow_run = render.workflows.run_flow(
  "my-workflow/format_message",
  ["hello"]
)

print(flow_run.status)
print(flow_run.results)
```

**Tab: Async client**

```python
flow_run = await render.workflows.run_flow(
  "my-workflow/format_message",
  ["hello"]
)

print(flow_run.status)
print(flow_run.results)
```

------

##### `flow_slug`

*Required.* The *slug* indicating the flow to run, available from your flow's page in the [Render Dashboard](https://dashboard.render.com).

Has the format `{workflow-slug}/{flow-name}` (e.g., `my-workflow/format_message`).

##### `input_data`

*Required.* A list or dictionary containing the flow's [input arguments](/workflows-defining-flows#function-arguments).

- *If you provide a list,* elements are positional based on the flow's function signature.
- *If you provide a dictionary,* each key maps to a parameter name in the flow's function signature.

For a flow that takes zero arguments, provide an empty list, `[]`.

##### `idempotency_key`

An optional string used to prevent duplicate flow runs.

For details, see [Idempotency keys](/workflows-running#idempotency-keys).

------

###### `list_flow_runs(params=None)`

Lists flow runs that match optional filters specified in a `ListFlowRunsParams` object.

*On success:* Returns a list of [`FlowRunWithCursor`](#flowrunwithcursor) objects.

*Raises:* [`ClientError`](#clienterror), [`RateLimitError`](#ratelimiterror), [`ServerError`](#servererror), [`TimeoutError`](#timeouterror)

**Tab: Sync client**

```python
from render.client.types import ListFlowRunsParams

params = ListFlowRunsParams(
  limit=10,
  flow_slug=["my-workflow/format_message"]
)

flow_runs = render.workflows.list_flow_runs(params)
for item in flow_runs:
  print(item.flow_run.id, item.cursor)
```

**Tab: Async client**

```python
from render.client.types import ListFlowRunsParams

params = ListFlowRunsParams(
  limit=10,
  flow_slug=["my-workflow/format_message"]
)

flow_runs = await render.workflows.list_flow_runs(params)
for item in flow_runs:
  print(item.flow_run.id, item.cursor)
```

The optional `params` object supports the following fields:

| Parameter | Description |
| --- | --- |
| `limit` | An integer specifying the maximum number of flow runs to return. |
| `cursor` | A cursor string for pagination. Use this to retrieve the next page of results. |
| `flow_slug` | A list of flow slugs to filter results by. Only flow runs for these flows are returned. |
| `flow_id` | A list of flow definition IDs to filter results by. |
| `status` | A list of [flow run status](#flowrunstatus) values to filter results by. Accepts `FlowRunStatus` enum values or strings. |
| `owner_id` | A list of workspace IDs to filter results by. Only flow runs from these workspaces will be returned. |
| `workflow_version_id` | A list of workflow version IDs to filter results by. |
| `workflow_id` | A list of workflow IDs to filter results by. |

###### `get_flow_run(flow_run_id)`

Retrieves the details of the flow run with the specified ID.

*On success:* Returns a [`FlowRunDetails`](#flowrundetails) object, regardless of whether the run is still in progress or has finished. A failed status does not itself raise an execution error.

*Raises:* [`ClientError`](#clienterror), [`RateLimitError`](#ratelimiterror), [`ServerError`](#servererror), [`TimeoutError`](#timeouterror)

**Tab: Sync client**

```python
details = render.workflows.get_flow_run("frn-abc123")
print(details.status.value, details.results)
```

**Tab: Async client**

```python
details = await render.workflows.get_flow_run("frn-abc123")
print(details.status.value, details.results)
```

| Parameter | Description |
| --- | --- |
| `flow_run_id` | *Required.* The ID of the flow run to retrieve. Has the format `frn-abc123...` |

###### `cancel_flow_run(flow_run_id)`

Cancels the flow run with the specified ID.

*On success:* Returns `None`.

*Raises:* [`ClientError`](#clienterror), [`RateLimitError`](#ratelimiterror), [`ServerError`](#servererror), [`TimeoutError`](#timeouterror)

**Tab: Sync client**

```python
render.workflows.cancel_flow_run("frn-abc123")
```

**Tab: Async client**

```python
await render.workflows.cancel_flow_run("frn-abc123")
```

| Parameter | Description |
| --- | --- |
| `flow_run_id` | *Required.* The ID of the flow run to cancel. Has the format `frn-abc123...` |

### Task types

###### `AwaitableTaskRun`

Represents a started task run returned by the async [`workflows.start_task`](#start-task) method.

You can `await` this object to wait for completion and obtain a [`TaskRunDetails`](#taskrundetails) object:

```python
started_run = await render.workflows.start_task(
  "my-workflow/calculate_square",
  [2]
)
finished_run = await started_run
print(finished_run.results) # [4]
```

| Member | Description |
| --- | --- |
| `id` | The task run's ID, available immediately. Has the format `trn-abc123...`. |
| `status` | The initial status of the task run, as a string. This value is usually `pending`. See [Run status](#run-status). |
| `is_terminal_status()` | Returns whether the initial `status` is terminal (one of `completed`, `failed`, or `canceled`). |

###### `TaskAttempt`

Represents one execution attempt of a particular task run. Returned in a [`TaskRun`](#taskrun) object's `attempts` list.

Each run's initial attempt has "attempt number" `0`. If an attempt fails and the run retries, each retry creates a new attempt with an incremented number.

| Property | Description |
| --- | --- |
| `task_run_id` | The ID of the task run this attempt belongs to. |
| `attempt` | The zero-based "attempt number": `0` for the initial attempt, `1` for the first retry, and so on. |
| `status` | The current status of this attempt, as a `TaskRunStatus` enum value. See [Run status](#run-status). |

###### `TaskAttemptDetails`

Provides the full details of an individual attempt. Returned in the [`TaskRunDetails`](#taskrundetails) object's `attempts` list.

*Includes all of the same properties as a [`TaskAttempt`](#taskattempt) object, plus:*

| Property | Description |
| --- | --- |
| `error` | The error message if the attempt failed. Otherwise, this value is the empty string. |
| `results` | A list containing the attempt's return value, if it completed successfully. Otherwise, `None`. |

###### `TaskRun`

Summarizes the state of a task run. Obtained in any of the following ways:

- From the sync [`workflows.start_task`](#start-task) method
- The `task_run` property of a [`TaskRunWithCursor`](#taskrunwithcursor) returned by [`workflows.list_task_runs`](#list-task-runs)

To obtain full details, including results, pass the run's ID to [`workflows.get_task_run`](#get-task-run).

| Property | Description |
| --- | --- |
| `id` | The ID of the task run. Has the format `trn-abc123...`. |
| `task_id` | The ID of the run's associated task. Has the format `tsk-abc123...`. |
| `status` | The current status of the task run, as a `TaskRunStatus` enum value. See [Run status](#run-status). |
| `started_at` | The `datetime` when the task run was first created and queued for execution. |
| `completed_at` | The `datetime` when the task run finished (successfully or otherwise). This value is `UNSET` until the run reaches a terminal status. |
| `parent_task_run_id` | The ID of this run's parent run, if this run was chained. For a root-level run, this value is the empty string. |
| `parent_task_attempt` | The zero-based attempt number of the parent task run that chained this run. `0` indicates the parent's initial attempt, `1` its first retry, and so on. This value is `UNSET` for a root-level run. |
| `root_task_run_id` | The ID of the root task run in this run's execution chain. For a root-level run, this value matches `id`. |
| `retries` | The number of times the task run has retried so far. For a newly started run, this is `0`. |
| `attempts` | A list of [`TaskAttempt`](#taskattempt) objects representing each execution attempt so far, including retries. |

###### `TaskRunDetails`

Provides the full details of a task run. Obtained in any of the following ways:

- Awaiting an [`AwaitableTaskRun`](#awaitabletaskrun) returned by the async [`workflows.start_task`](#start-task) method
- Calling [`workflows.run_task`](#run-task)
- Calling [`workflows.get_task_run`](#get-task-run)
- Iterating over [`workflows.task_run_events`](#task-run-events)

*Includes all of the same properties as a [`TaskRun`](#taskrun) object, plus:*

------

##### `results`

A list containing the task's return value(s).

This value is `None` unless `status` is `completed`.

##### `input_`

The argument values that were passed to the task run, in one of the following formats:

- A list with values matching the positional arguments of the task's function signature
- An object with keys matching the named arguments of the task's function signature

The format matches whichever format the task input was originally provided in.

If this value is an object, you can call its `to_dict()` method to obtain a raw dictionary.

Note the trailing underscore (`_`) in this property name.

##### `error`

The error message if the task run failed. Otherwise, `UNSET`.

##### `attempts`

A list of [`TaskAttemptDetails`](#taskattemptdetails) objects representing each individual execution attempt for this run.

------

###### `TaskRunWithCursor`

Represents an item returned by [`workflows.list_task_runs`](#list-task-runs). It pairs a task run with its pagination cursor.

| Property | Description |
| --- | --- |
| `task_run` | The [`TaskRun`](#taskrun) returned in this item. |
| `cursor` | The cursor associated with this item. Pass it as `ListTaskRunsParams(cursor=...)` to retrieve the next page of results. |

### Flow types

###### `FlowRun`

Summarizes the state of a flow run. Obtained in any of the following ways:

- From the [`workflows.start_flow`](#start-flow) method (sync or async)
- The `flow_run` property of a [`FlowRunWithCursor`](#flowrunwithcursor) returned by [`workflows.list_flow_runs`](#list-flow-runs)

To obtain full details, including results, pass the run's ID to [`workflows.get_flow_run`](#get-flow-run).

| Property | Description |
| --- | --- |
| `id` | The ID of the flow run. Has the format `frn-abc123...`. |
| `flow_id` | The ID of the run's associated flow. Has the format `flw-abc123...`. |
| `status` | The current status of the flow run, as a [`FlowRunStatus`](#flowrunstatus) enum value. |
| `started_at` | The `datetime` when the flow run was first created and queued for execution. |
| `completed_at` | The `datetime` when the flow run finished (successfully or otherwise). This value is `UNSET` until the run reaches a terminal status. |

###### `FlowRunDetails`

Provides the full details of a flow run. Obtained by calling [`workflows.get_flow_run`](#get-flow-run).

*Includes all of the same properties as a [`FlowRun`](#flowrun) object, plus:*

------

###### Property

`results`

###### Description

A list containing the flow's return value(s).

This value is an empty list unless `status` is `succeeded`.

---

###### Property

`input_`

###### Description

The argument values that were passed to the flow run, in one of the following formats:

- A list with values matching the positional arguments of the flow's function signature
- An object with keys matching the named arguments of the flow's function signature

The format matches whichever format the flow input was originally provided in.

If this value is an object, you can call its `to_dict()` method to obtain a raw dictionary.

Note the trailing underscore (`_`) in this property name.

---

###### Property

`error`

###### Description

The error message if the flow run failed. Otherwise, `UNSET`.

---

###### Property

`error_details`

###### Description

Details for a determinism or limit error. See fields below. Otherwise, `UNSET`.

---

###### Property

`attempts`

###### Description

A list of objects representing each individual execution attempt for this run. See all supported fields below.

---

###### *`attempts` fields*

---

###### Property

`attempt`

###### Description

The zero-based "attempt number": `0` for the initial attempt, `1` for the first recovery attempt, and so on.

---

###### Property

`status`

###### Description

The current status of this attempt, as a [`FlowRunStatus`](#flowrunstatus) enum value.

---

###### Property

`started_at`

###### Description

The `datetime` when the attempt started executing.

---

###### Property

`completed_at`

###### Description

The `datetime` when the attempt finished (successfully or otherwise). This value is `UNSET` until the attempt reaches a terminal status.

---

###### Property

`error`

###### Description

The error message if the attempt failed. Otherwise, `UNSET`.

---

###### *`error_details` fields*

---

###### Property

`code`

###### Description

The error code: `flow_determinism_error`, `flow_step_limit_exceeded`, `flow_payload_too_large`, or `flow_history_too_large`.

Read the enum's `.value` property to obtain the string.

---

###### Property

`message`

###### Description

A description of the failure.

---

###### Property

`seq`

###### Description

The zero-based sequence number of the step that caused the determinism error.

---

###### Property

`flow_attempt`

###### Description

The zero-based "attempt number" associated with the determinism error.

---

###### Property

`kind`

###### Description

For determinism errors: `input_hash` (name or input changed), `type`, or `unknown_type`.

Read the enum's `.value` property to obtain the string.

---

###### Property

`expected, actual`

###### Description

Descriptions of the recorded and attempted calls for a determinism error.

---

###### Property

`limit, value`

###### Description

The applicable limit and the value that exceeded it.

------

###### `FlowRunStatus`

The status of a flow run or flow attempt, as an enum value. Import it from `render.client.types` and read its `.value` property to obtain the status string.

A successful flow run has status `succeeded` (task runs use `completed`). The terminal statuses are `succeeded`, `failed`, and `canceled`.

```python
from render.client.types import FlowRunStatus

if details.status == FlowRunStatus.SUCCEEDED:
  print(details.results)
```

| Value | Description |
| --- | --- |
| `pending` | The run has been queued but has not yet started executing. |
| `running` | The run is currently executing. |
| `succeeded` | The run finished successfully. |
| `failed` | The run failed. |
| `canceled` | The run was canceled. |

###### `FlowRunWithCursor`

Represents an item returned by [`workflows.list_flow_runs`](#list-flow-runs). It pairs a flow run with its pagination cursor.

| Property | Description |
| --- | --- |
| `flow_run` | The [`FlowRun`](#flowrun) returned in this item. |
| `cursor` | The cursor associated with this item. Pass it as `ListFlowRunsParams(cursor=...)` to retrieve the next page of results. |

### Run status

For flow runs and attempts, see [`FlowRunStatus`](#flowrunstatus). The table below describes task runs and attempts.

The `status` property on [`TaskRun`](#taskrun), [`TaskRunDetails`](#taskrundetails), [`TaskAttempt`](#taskattempt), and [`TaskAttemptDetails`](#taskattemptdetails) is a `TaskRunStatus` enum value. Read its `.value` property to obtain the status string. The `status` property on [`AwaitableTaskRun`](#awaitabletaskrun) is already a string.

For example, to check a returned run's status:

```python
from render.client.types import TaskRunStatus

if task_run.status == TaskRunStatus.COMPLETED:
  print(task_run.results)
```

The exact meaning of each status differs for a task attempt and a task run, as shown:

| Value | Task attempt | Task run |
| --- | --- | --- |
| `pending` | The attempt has been queued but has not yet started executing. | The run's current attempt is `pending`. |
| `running` | The attempt is currently executing. | The run's current attempt is `running`. |
| `paused` | The attempt is waiting for the result of a chained run. | The run's current attempt is `paused`. |
| `completed` | The attempt finished successfully. | The run finished successfully. |
| `failed` | The attempt finished with an error. | All of the run's attempts have finished with an error and no retries remain. |
| `canceled` | The run was canceled before this attempt finished. | The run was canceled before it finished. |

## Error types

The Render SDK defines the custom error types below. In certain cases, the SDK also raises standard Python errors, such as `ValueError` and `TypeError`.

### Client and task errors

```python
from render.client.errors import (
  RenderError,
  ClientError,
  RateLimitError,
  ServerError,
  TimeoutError,
  ExecutionError,
  TaskRunError,
  FlowRunError
)
```

###### `RenderError(Exception)`

The base class for custom Render SDK errors.

###### `ClientError(RenderError)`

Raised when a request to the Render API returns a 400-level error code.

Common causes include:

- Invalid API key
- Invalid task or flow identifier
- Invalid task or flow arguments
- Invalid action (for example, starting a run on a suspended workflow service)

###### `RateLimitError(ClientError)`

Raised when a request to the Render API returns a 429 (rate limit exceeded) error code.

###### `ServerError(RenderError)`

Raised when a request to the Render API returns a 500-level error code, and for network connection failures.

###### `TimeoutError(RenderError)`

Raised when a request to the Render API times out. This custom SDK definition is distinct from Python's built-in `TimeoutError`.

###### `ExecutionError(RenderError)`

The base class for errors from a failed task run or flow step.

###### `TaskRunError(ExecutionError)`

Raised in the following cases:

- A [chained run](#async-taskcontext-run) fails.
- A run fails while awaiting an [`AwaitableTaskRun`](#awaitabletaskrun).
- A run fails while calling [`workflows.run_task`](#run-task).

###### `FlowRunError(RenderError)`

Raised when a flow run fails or is canceled while calling [`workflows.run_flow`](#run-flow).

### Flow execution errors

These errors are raised during flow execution, such as during a call to [`ctx.step()`](#flowcontext-step).

Import these errors from `render.client.errors`.

```python
from render.client.errors import (
  TerminalFlowError,
  DeterminismError,
  FlowBadRequestError,
  FlowLimitError
)
```

###### `TerminalFlowError(ExecutionError)`

Raised when a step fails after exhausting its retries. The flow run fails even if flow code catches the error.

For retry guidance, see [Retry settings](/workflows-defining-flows#retry-settings).

###### `DeterminismError(TerminalFlowError)`

Raised when a step call does not match the recorded history during replay. This fails the flow run even if flow code catches the error.

When available, the `details` object contains the following:

------

##### `step_key.seq`

The zero-based sequence number of the step that caused the determinism error.

##### `workflow_attempt`

The zero-based "attempt number" associated with the recorded step.

##### `kind`

How the replay differs from the recorded history. Read the enum's `.value` property to obtain the string:

- `input_hash`: The step's name or inputs changed.
- `type`: The type of step changed.
- `unknown_type`: The recorded step type is not recognized by the SDK.

##### `expected`

A description of the call recorded in the step history.

##### `actual`

A description of the call made during replay.

------

###### `FlowBadRequestError(TerminalFlowError)`

Raised when a step operation is rejected by the workflow system. The `code` property contains its error code, or `None` if no code was supplied.

The flow run fails even if flow code catches the error.

###### `FlowLimitError(FlowBadRequestError)`

Raised when a step operation exceeds a [workflow limit](/workflows-limits#additional-limits). The flow run fails even if flow code catches the error.

The `code` identifies the limit, such as `flow_payload_too_large`. The `limit` and `value` properties contain the applicable limit and the value that exceeded it, or `None` if not supplied by the runtime.


---

##### Appendix: Glossary definitions

###### task

A function that executes on its own provisioned *instance*. Belongs to a *workflow*.

Each execution of a task is called a *run*.

Related article: https://render.com/docs/workflows-defining-tasks.md

###### flow

A function that executes a durable sequence of *steps* on its own *instance*. Belongs to a *workflow*.

If a flow's *run* is interrupted, it can reuse the results from completed steps when it replays.

Related article: https://render.com/docs/workflows-defining-flows.md

###### run chaining

Triggering a new *task run* directly from an in-progress run.

All runs in a chain belong to the same *workflow*.

Related article: https://render.com/docs/workflows-defining-tasks.md#chaining-task-runs

###### run

A single execution of a *task* or *flow*. Belongs to a *workflow*.

A run spins up in its own *instance*, executes, returns a value, and is deprovisioned.

Related article: https://render.com/docs/workflows-running.md

###### compute plan

Specifies the CPU and RAM available to your service's *instances*.

Common compute plans for a new web service include:

- *`free`*: 0.1 CPU / 512 MB RAM
- *`0.5c-512mb`*: 0.5 CPU / 512 MB RAM
- *`1c-2g`*: 1 CPU / 2 GB RAM

Compute plans were previously known as *instance types*.

Related article: https://render.com/docs/compute-plans.md

###### instance

A containerized environment that runs your service's code on Render.

You can select from a range of *compute plans* with different CPU and RAM specs.