> ## Documentation Index
> Fetch the complete documentation index at: https://developer.opus.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a Workflow

> Create a workflow in a workspace, optionally pre-populated with nodes and edges

Create a workflow in the workspace named by the `x-workspace-id` header.

`nodes` and `edges` are optional — omit them and Opus creates the workflow with an input node and an output node already wired, ready to open in the [Builder](/guides/builder). Supply them to stand up a complete graph in a single call.

Requires the **Workflow: Full** permission in the target workspace.

<Note>
  Prefer to describe the workflow in plain language instead of building the graph yourself? Use [Generate Workflow](/api-reference/v1-workflow-generation/generate-headless) and let Opus AI agents design it for you.
</Note>

## Headers

<ParamField header="x-service-key" type="string" required>
  Your API authentication key
</ParamField>

<ParamField header="x-workspace-id" type="string" required>
  UUID of the workspace to create the workflow in
</ParamField>

## Body Parameters

<ParamField body="name" type="string" required>
  Workflow name
</ParamField>

<ParamField body="description" type="string">
  Workflow description
</ParamField>

<ParamField body="nodes" type="object">
  Workflow nodes keyed by node ID. Each map **key must equal the `id` of the node it holds**.

  A node's `handler_class` determines the shape of its `process` — see [Node Process Types](/api-reference/v1-workflow-generation/node-process-types).

  <Expandable title="Node object">
    <ParamField body="id" type="string" required>
      Node ID (UUID). Must match the key this node is stored under.
    </ParamField>

    <ParamField body="name" type="string" required>
      Node name
    </ParamField>

    <ParamField body="type" type="string" required>
      Node type — for example `input`, `output`, `agent`, `integration`, `code`
    </ParamField>

    <ParamField body="description" type="string">
      Node description
    </ParamField>

    <ParamField body="handler_id" type="string">
      Handler ID
    </ParamField>

    <ParamField body="handler_version_id" type="string">
      Handler version ID
    </ParamField>

    <ParamField body="handler_active_id" type="string">
      Active handler ID
    </ParamField>

    <ParamField body="handler_active_version_id" type="string">
      Active handler version ID
    </ParamField>

    <ParamField body="handler_class" type="string">
      Which variety of this node type — it determines the shape of `process`. See [Node Process Types](/api-reference/v1-workflow-generation/node-process-types).
    </ParamField>

    <ParamField body="properties" type="string[]">
      Node properties. Set `route` when the node defines [routes](#routing).
    </ParamField>

    <ParamField body="input_schema" type="object">
      Input variable schema — see [Variable Schemas](/api-reference/v1-workflow-generation/variable-schemas)
    </ParamField>

    <ParamField body="output_schema" type="object">
      Output variable schema — see [Variable Schemas](/api-reference/v1-workflow-generation/variable-schemas)
    </ParamField>

    <ParamField body="mappings" type="object">
      Variable mappings — which upstream variable feeds each input. See [Connecting Variables Between Nodes](/api-reference/v1-workflow-generation/variable-schemas#connecting-variables-between-nodes).
    </ParamField>

    <ParamField body="process" type="object">
      The step's configuration. Its shape is determined by `handler_class` — see [Node Process Types](/api-reference/v1-workflow-generation/node-process-types).
    </ParamField>

    <ParamField body="routes" type="object">
      Conditional branches out of this node, keyed by route ID. Each map **key must equal the `id` of the route it holds**.

      <Expandable title="Route object">
        <ParamField body="id" type="string" required>
          Route ID. Must match the key this route is stored under.
        </ParamField>

        <ParamField body="name" type="string" required>
          Route name
        </ParamField>

        <ParamField body="description" type="string">
          What this route is for
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="set_values" type="object">
      Fixed input/output values — see [Fixed Values](/api-reference/v1-workflow-generation/variable-schemas#fixed-values)
    </ParamField>

    <ParamField body="position" type="object">
      Where the node sits on the Builder canvas. Both values are integers.

      <Expandable title="Position object">
        <ParamField body="x" type="integer" required>
          Horizontal position
        </ParamField>

        <ParamField body="y" type="integer" required>
          Vertical position
        </ParamField>
      </Expandable>

      Omit it and the node is still valid — position is presentational only and never affects execution.
    </ParamField>

    <ParamField body="public_execution_settings" type="object">
      Public execution settings
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="edges" type="object">
  Workflow edges keyed by edge ID. Each map **key must equal the `id` of the edge it holds**, and `from_node_id` and `to_node_id` must name nodes present in `nodes`.

  <Expandable title="Edge object">
    <ParamField body="id" type="string" required>
      Edge ID (UUID). Must match the key this edge is stored under.
    </ParamField>

    <ParamField body="from_node_id" type="string" required>
      Source node ID (UUID)
    </ParamField>

    <ParamField body="to_node_id" type="string" required>
      Target node ID (UUID)
    </ParamField>

    <ParamField body="label" type="string">
      Edge label. One of `soft` or `hard`.
    </ParamField>

    <ParamField body="route_id" type="string">
      ID of the route this edge belongs to
    </ParamField>

    <ParamField body="route_complement" type="boolean">
      Whether this edge is the complement of its route
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="activeStatus" type="string" default="inactive">
  Workflow active status. One of `active` or `inactive`.
</ParamField>

<ParamField body="orgUnitIds" type="string[]">
  Org units to tag the new workflow to at creation. Maximum 100.

  Tagging is best-effort: if a tag can't be applied, the workflow is still created and the call still succeeds.
</ParamField>

<Warning>
  Node and edge objects use **snake\_case** keys (`from_node_id`, `input_schema`), while the top-level body fields use **camelCase** (`activeStatus`, `orgUnitIds`). This is deliberate — nodes and edges are forwarded to the workflow executor verbatim.
</Warning>

## Routing

A route is a conditional branch out of a node. Wiring one up touches four places, and all four must agree or the route is orphaned:

<Steps>
  <Step title="Declare the route">
    Add an entry to the node's `routes`, keyed by the route ID.
  </Step>

  <Step title="Back it with a boolean output variable">
    Add a `bool` variable to the node's `output_schema` whose **schema key is the route ID**. This variable is what the node sets at runtime to decide whether the branch is taken.
  </Step>

  <Step title="Mark the node">
    Include `route` in the node's `properties`.
  </Step>

  <Step title="Point the edges at it">
    Set `route_id` on each edge that belongs to the route. Use `route_complement` on the edge that should be taken when the route's boolean is false.
  </Step>
</Steps>

```json routing theme={null}
{
  "routes": {
    "{ROUTE_ID}": {
      "id": "{ROUTE_ID}",
      "name": "Needs Approval",
      "description": "Taken when the amount exceeds the auto-approval limit"
    }
  },
  "properties": ["route"],
  "output_schema": {
    "schema": {
      "{ROUTE_ID}": {
        "id": "{ROUTE_ID}",
        "variable_name": "needs_approval",
        "allowed_types": [{ "type": "bool" }]
      }
    },
    "variable_addition_allowed": true
  }
}
```

The matching edges then carry the route:

```json edges theme={null}
"edges": {
  "{EDGE_A}": {
    "id": "{EDGE_A}",
    "from_node_id": "{NODE_ID}",
    "to_node_id": "{APPROVAL_NODE_ID}",
    "route_id": "{ROUTE_ID}"
  },
  "{EDGE_B}": {
    "id": "{EDGE_B}",
    "from_node_id": "{NODE_ID}",
    "to_node_id": "{SKIP_NODE_ID}",
    "route_id": "{ROUTE_ID}",
    "route_complement": true
  }
}
```

## Response

<ResponseField name="workflowId" type="string" required>
  The workflow ID. Use this as `{workflowId}` in [Get Workflow Details](/api-reference/v1-workflow-generation/get-workflow-details), [Update a Workflow](/api-reference/v1-workflow-generation/update-workflow), and [Get Run Status](/api-reference/v1-workflow-generation/get-run-status).
</ResponseField>

<ResponseField name="entityId" type="string" required>
  The ID of the workflow's entry in the Opus catalog. Most integrations only need `workflowId`.
</ResponseField>

<ResponseField name="workflowVersionId" type="string" required>
  The ID of the first workflow version
</ResponseField>

<ResponseField name="builderType" type="string" required>
  Builder type. One of `OPUS_V1` or `OPUS_V2`.
</ResponseField>

## Errors

<ResponseField name="400" type="Bad Request">
  Missing or malformed `x-workspace-id` header.
</ResponseField>

<ResponseField name="401" type="Unauthorized">
  Missing, invalid, or expired API key.
</ResponseField>

<ResponseField name="403" type="Forbidden">
  No access to that workspace.
</ResponseField>

<ResponseField name="404" type="Not Found">
  The workspace or user does not exist.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  # Minimal — Opus adds the input and output nodes for you
  curl --request POST \
    --url https://operator.opus.com/api/v1/workflow \
    --header 'x-service-key: {YOUR_SERVICE_KEY}' \
    --header 'x-workspace-id: {YOUR_WORKSPACE_ID}' \
    --header 'Content-Type: application/json' \
    --data '{
      "name": "Quarterly Report Processor",
      "description": "Summarizes quarterly financial reports"
    }'
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://operator.opus.com/api/v1/workflow",
      headers={
          "x-service-key": "{YOUR_SERVICE_KEY}",
          "x-workspace-id": "{YOUR_WORKSPACE_ID}",
      },
      json={
          "name": "Quarterly Report Processor",
          "description": "Summarizes quarterly financial reports",
          "activeStatus": "inactive",
      },
  )

  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://operator.opus.com/api/v1/workflow", {
    method: "POST",
    headers: {
      "x-service-key": "{YOUR_SERVICE_KEY}",
      "x-workspace-id": "{YOUR_WORKSPACE_ID}",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "Quarterly Report Processor",
      description: "Summarizes quarterly financial reports",
    }),
  });

  const data = await response.json();
  console.log(data);
  ```

  ```json With nodes and edges theme={null}
  {
    "name": "Quarterly Report Processor",
    "description": "Summarizes quarterly financial reports",
    "activeStatus": "inactive",
    "nodes": {
      "{INPUT_NODE_ID}": {
        "id": "{INPUT_NODE_ID}",
        "name": "Workflow Input",
        "type": "input"
      },
      "{OUTPUT_NODE_ID}": {
        "id": "{OUTPUT_NODE_ID}",
        "name": "Workflow Output",
        "type": "output"
      }
    },
    "edges": {
      "{EDGE_ID}": {
        "id": "{EDGE_ID}",
        "from_node_id": "{INPUT_NODE_ID}",
        "to_node_id": "{OUTPUT_NODE_ID}"
      }
    }
  }
  ```
</RequestExample>

<ResponseExample>
  ```json 201 Response theme={null}
  {
    "entityId": "{ENTITY_ID}",
    "workflowId": "{WORKFLOW_ID}",
    "workflowVersionId": "{WORKFLOW_VERSION_ID}",
    "builderType": "OPUS_V2"
  }
  ```
</ResponseExample>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.