Workflows
API reference for enhanced workflow methods.
The workflows resource is enhanced with helper methods on response objects. All original methods (retrieve, create, update, list, delete, run, userApproval, userSelection, getTags) are inherited.
workflow.findNode(nodeId)
Section titled “workflow.findNode(nodeId)”Available on response.workflow after calling client.workflows.retrieve().
Signature
response.workflow.findNode(nodeId: string): Flow | undefinedParameters
| Name | Type | Description |
|---|---|---|
nodeId |
string |
The ID of the node to find. |
Returns
Flow | undefined — The node definition, or undefined if not found.
| Property | Type | Description |
|---|---|---|
id |
string |
Node identifier. |
type |
string |
Node type: 'custom-model', 'for-each', 'generate-prompt', 'list', 'logic', 'model', 'remove-background', 'transform', 'user-approval', 'user-selection', 'workflow'. |
modelId? |
string |
Model ID (for custom-model nodes). |
workflowId? |
string |
Sub-workflow ID (for workflow nodes). |
inputs? |
Input[] |
Node input configuration. |
dependsOn? |
string[] |
IDs of nodes this node depends on. |
logic? |
Logic |
Logic configuration (for logic nodes). |
Example
const response = await client.workflows.retrieve('wflow_...');const node = response.workflow.findNode('final_image');
if (node) { console.log(node.type); // 'custom-model' console.log(node.modelId); // 'model_...'}workflow.getNodesByType(type)
Section titled “workflow.getNodesByType(type)”Available on response.workflow after calling client.workflows.retrieve().
Signature
response.workflow.getNodesByType(type: NodeType): Flow[]Flow is WorkflowRetrieveResponse.Workflow.Flow and NodeType is Flow['type']. The SDK does not export these aliases; import WorkflowRetrieveResponse from @scenario-labs/sdk/resources/workflows to name them.
Parameters
| Name | Type | Description |
|---|---|---|
type |
NodeType |
One of: 'custom-model', 'for-each', 'generate-prompt', 'list', 'logic', 'model', 'remove-background', 'transform', 'user-approval', 'user-selection', 'workflow'. |
Returns
Flow[] — Array of matching node definitions. Empty array if none match.
Example
const response = await client.workflows.retrieve('wflow_...');const modelNodes = response.workflow.getNodesByType('custom-model');
modelNodes.forEach((node) => { console.log(`${node.id}: model=${node.modelId}`);});workflow.validate()
Section titled “workflow.validate()”Available on response.workflow after calling client.workflows.retrieve(). Validates the structural integrity of the workflow’s flow definition. Throws an Error with a human-readable message on the first violation found.
Signature
response.workflow.validate(): voidParameters
None.
Returns
void — returns nothing on success.
Errors
Throws Error on the first violation. Checks performed:
| # | Check |
|---|---|
| 1 | Every node has a non-empty string id. |
| 2 | Node IDs are unique within the flow. |
| 3 | Every node type is one of: 'custom-model', 'for-each', 'generate-prompt', 'list', 'logic', 'model', 'remove-background', 'transform', 'user-approval', 'user-selection', 'workflow'. |
| 4 | Per-type required fields: custom-model → modelId, workflow → workflowId, logic → logicType, for-each → non-empty loopBodyNodeIds or positive count, user-selection → exactly one input of type file_array or string_array (any other input must be type connection), with input.min ≤ input.max (both integers in 1–100, defaulting to 1 and 100). |
| 5 | All cross-references point to existing node IDs: dependsOn, input ref.node, input ref.conditional, and loopBodyNodeIds. |
| 6 | A node that an if-else branch can skip is read through a plain ref.node only by nodes gated by the same branch (see below). |
A node is skippable on an if-else branch when it has a connection input on that branch, or when every target of one of its ref.conditional inputs is skippable on the same branch. Any other node reading it through a plain ref.node (including inside items sub-inputs) would never resolve when the branch isn’t taken, so the workflow job would never complete. A ref.conditional merge reads whichever branch ran, so it is allowed.
To fix a violation, gate the reader through the same branch, or merge the branches with a ref.conditional.
Example
const response = await client.workflows.retrieve('wflow_...');
try { response.workflow.validate(); console.log('Workflow is valid');} catch (err) { // e.g. 'Node "final_image" (custom-model) is missing required field "modelId"' console.error((err as Error).message);}workflow.run(params)
Section titled “workflow.run(params)”Available on response.workflow after calling client.workflows.retrieve(). Runs this workflow. Shortcut for client.workflows.run(workflow.id, params) — no need to repeat the workflow ID. Returns an enhanced response where response.job has .wait(). See Jobs for the full .wait() reference.
If the workflow was retrieved with a projectId (the typed projectId param or options.query.projectId on retrieve(), or the client’s projectId), run() reuses that project, and response.job.wait() polls in it too.
Signature
response.workflow.run( params: WorkflowRunParams, options?: RequestOptions,): APIPromise<WithJob<WorkflowRunResponse>>Parameters
| Name | Type | Description |
|---|---|---|
params.body |
unknown |
Workflow input values (keys defined by the workflow’s inputs). |
params.dryRun? |
'true' | 'api' |
Estimates the creative units cost (269 response) instead of running. 'api' estimates at the API tier. The response then carries only the cost estimate (creativeUnitsCost) and no job, so don’t call .wait() on it. |
params.projectId? |
string |
Project that owns the resource. |
An explicit non-empty projectId on this call, in params or options.query, wins over the captured project. An empty or unset one does not.
Returns
APIPromise<WithJob<WorkflowRunResponse>> — a run response where response.job has .wait().
Example
const { workflow } = await client.workflows.retrieve('wflow_...');
const response = await workflow.run({ body: { prompt: 'a medieval landscape' },});
const completed = await response.job.wait();console.log(completed.status); // 'success'console.log(completed.metadata?.assetIds);run() — enhanced response
Section titled “run() — enhanced response”client.workflows.run() returns a response where response.job has .wait(). See Jobs for the full .wait() reference.
Example
const response = await client.workflows.run('wflow_...', { body: { prompt: 'a sleek platinum bob with bangs', image: 'asset_...', },});
// response.job has all original fields + .wait()const completed = await response.job.wait();
// Access completed job metadata (flow with runtime status + assets)completed.metadata?.flow?.forEach((node) => { console.log(`${node.id} [${node.status}]`); node.assets?.forEach((asset) => { console.log(` ${asset.assetId} -> ${asset.url}`); });});userApproval() — enhanced response
Section titled “userApproval() — enhanced response”client.workflows.userApproval() also returns a response where response.job has .wait().
const response = await client.workflows.userApproval('wflow_...', { projectId: 'proj_...', nodeId: 'approval_node', workflowJobId: 'job_...', action: 'approve', // or 'reject'; defaults to 'approve'});
const completed = await response.job.wait();client.workflows.userSelection(), which answers a user-selection node (projectId, nodeId, workflowJobId, action: 'select' | 'reject', and selectedIndices when selecting), is enhanced like userApproval(): its response.job gains .wait().
validateWorkflowFlow(flow) — standalone utility
Section titled “validateWorkflowFlow(flow) — standalone utility”Named export from @scenario-labs/sdk. Use it to validate a raw flow array (e.g. one you’ve built locally or loaded from JSON) without going through retrieve(). This is the same function workflow.validate() calls internally, so the checks and error messages are identical.
Signature
validateWorkflowFlow(flow: unknown[]): voidParameters
| Name | Type | Description |
|---|---|---|
flow |
unknown[] |
The workflow flow (array of node definitions) to validate. |
Returns
void — returns nothing on success. Throws Error on the first violation.
Example
import { validateWorkflowFlow } from '@scenario-labs/sdk';
const flow = [ { id: 'start', type: 'generate-prompt', inputs: [] }, { id: 'final_image', type: 'custom-model', modelId: 'model_...', dependsOn: ['start'] },];
try { validateWorkflowFlow(flow);} catch (err) { console.error((err as Error).message);}validateEditorInfo(data) — import validator
Section titled “validateEditorInfo(data) — import validator”Named export from @scenario-labs/sdk. Validates a workflow export payload (e.g. from a JSON file import). Throws a WorkflowImportError with a human-readable message on the first violation found. Returns the validated data typed as WorkflowExportData.
Signature
validateEditorInfo(data: unknown): WorkflowExportDataParameters
| Name | Type | Description |
|---|---|---|
data |
unknown |
The parsed JSON payload to validate. |
Returns
WorkflowExportData — the validated export payload.
| Property | Type | Description |
|---|---|---|
version |
'1.0' |
Payload version; only '1.0' is accepted. |
name |
string |
Workflow name. |
description |
string |
Workflow description. |
editorInfo.nodes |
Array<{ type: WorkflowEditorNodeType; ... }> |
Editor nodes. Valid type values: 'text', 'asset', 'aspectRatio', 'model', 'llm', 'transformText', 'splitText', 'ifElse', 'groupItems', 'sliceAssets', 'forEach', 'forEachEnd', 'stickyNote', 'approval', 'modelInput'. |
editorInfo.edges |
object[] |
Edge connections between nodes. |
editorInfo.inputKeys |
string[] |
Input key names. |
inputs |
unknown[] |
Input definitions. |
tagSet |
string[] |
Tags associated with the workflow. |
uiConfig? |
object |
Optional UI configuration. |
exportedAt |
string |
ISO 8601 export timestamp. |
exportedBy |
string |
Identifier of the exporting entity. |
Errors
Throws WorkflowImportError on the first violation. Checks performed:
| # | Check |
|---|---|
| 1 | data is a non-null object. |
| 2 | version is present and equals '1.0'. |
| 3 | editorInfo is present, and its nodes and edges are arrays (they may be empty). |
| 4 | inputs is an array. |
| 5 | Each node type is a recognized WorkflowEditorNodeType. |
Example
import { validateEditorInfo, WorkflowImportError } from '@scenario-labs/sdk';import fs from 'node:fs';
const raw = JSON.parse(fs.readFileSync('my-workflow-export.json', 'utf8'));
try { const data = validateEditorInfo(raw); console.log(data.name); // 'My workflow' console.log(data.version); // '1.0'} catch (err) { if (err instanceof WorkflowImportError) { // e.g. 'Unsupported version: 2.0' console.error(err.message); }}WorkflowEditorNodeType
Section titled “WorkflowEditorNodeType”String-literal union of valid editor node type values. Used by validateEditorInfo() to check each node’s type in editorInfo.nodes.
type WorkflowEditorNodeType = | 'text' | 'asset' | 'aspectRatio' | 'model' | 'llm' | 'transformText' | 'splitText' | 'ifElse' | 'groupItems' | 'sliceAssets' | 'forEach' | 'forEachEnd' | 'stickyNote' | 'approval' | 'modelInput';The runtime constant VALID_EDITOR_NODE_TYPES (also exported) contains the same values as a readonly array and is used internally by validateEditorInfo.
WorkflowImportError
Section titled “WorkflowImportError”Error subclass thrown by validateEditorInfo() when validation fails. Extends the standard Error; its name property is always 'WorkflowImportError', which lets you distinguish import validation errors from other runtime errors.
Example
import { WorkflowImportError, validateEditorInfo } from '@scenario-labs/sdk';
try { validateEditorInfo(raw);} catch (err) { if (err instanceof WorkflowImportError) { console.error('Import validation failed:', err.message); } else { throw err; }}convertWorkflowEditorToFlow(params) — editor-to-API converter
Section titled “convertWorkflowEditorToFlow(params) — editor-to-API converter”Named export from @scenario-labs/sdk. Converts a workflow editor state (nodes, edges, input key order) into a flow array ready to be passed to client.workflows.update({ flow: ... }).
Signature
convertWorkflowEditorToFlow( params: ConvertWorkflowEditorToFlowParams,): WorkflowEditorFlowItem[]Parameters
| Name | Type | Description |
|---|---|---|
params.nodes |
WorkflowEditorNode[] |
Editor nodes. See WorkflowEditorNode. |
params.edges |
WorkflowEditorEdge[] |
Edge connections between nodes. |
params.inputKeys |
string[] |
Ordered list of input node IDs; determines sort order of workflow inputs. |
params.getModel? |
(modelId: string) => WorkflowEditorModel | 'unknown' | undefined |
Optional model resolver. Called with a node’s modelId; return the model object, 'unknown', or undefined. Used to resolve aspect-ratio presets and input-type hints. |
Returns
WorkflowEditorFlowItem[] — the compiled flow array, suitable for client.workflows.update().
Example
import Scenario, { convertWorkflowEditorToFlow } from '@scenario-labs/sdk';import type { WorkflowUpdateParams } from '@scenario-labs/sdk/resources/workflows';
const client = new Scenario({ apiKey: '...', apiSecret: '...' });
const flow = convertWorkflowEditorToFlow({ nodes, edges, inputKeys, getModel });
// `convertWorkflowEditorToFlow` returns `WorkflowEditorFlowItem[]`, whose `type`// is a plain `string`. `update()` expects the API's `Flow` type, whose `type` is// a narrower string-literal union — so cast to it when passing the compiled flow.await client.workflows.update('wflow_...', { flow: flow as WorkflowUpdateParams.Flow[] });WorkflowEditorNode
Section titled “WorkflowEditorNode”Union type for workflow editor nodes. Each member has id, type, and a data object specific to that type.
type WorkflowEditorNode = | { id: string; type: 'model'; data: NodeCommonData & { modelId?: string; type?: string; form?: Record<string, unknown> } } | { id: string; type: 'llm'; data: NodeCommonData & { modelId?: string; form?: Record<string, unknown> } } | { id: string; type: 'text'; data: NodeCommonData & { value?: string } } | { id: string; type: 'asset'; data: NodeCommonData & { type?: string; value?: string | string[]; isMultiple?: boolean; isRequired?: boolean; valueFirstFrame?: string; valueLastFrame?: string } } | { id: string; type: 'transformText'; data: NodeCommonData & { value?: string } } | { id: string; type: 'splitText'; data: NodeCommonData & { splitDelimiter?: string } } | { id: string; type: 'aspectRatio'; data: NodeCommonData & { output?: string; quality?: string } } | { id: string; type: 'groupItems'; data: NodeCommonData & { value?: string } } | { id: string; type: 'sliceAssets'; data: NodeCommonData & { from?: number | string; count?: number | string; mode?: 'default' | 'randomize' | 'choose'; isRandomize?: boolean; randomSeed?: string; isSeedLocked?: boolean; message?: string; value?: string } } | { id: string; type: 'forEach'; data: NodeCommonData } | { id: string; type: 'forEachEnd'; data: NodeCommonData & { parentNodeId?: string } } | { id: string; type: 'ifElse'; data: NodeCommonData & { conditionBlocks?: WorkflowEditorConditionBlock[] } } | { id: string; type: 'approval'; data: NodeCommonData & { message?: string } } | { id: string; type: 'modelInput'; data: NodeCommonData & { inputName: string; form?: Record<string, unknown>; parentModelNodeId?: string; modelId?: string } };where NodeCommonData is:
type NodeCommonData = { isInput?: boolean; isOutput?: boolean; inputHandles?: WorkflowEditorHandleInput[]; outputHandles?: WorkflowEditorHandleOutput[];};WorkflowEditorEdge
Section titled “WorkflowEditorEdge”type WorkflowEditorEdge = { source: string; target: string; sourceHandle?: string | null; targetHandle?: string | null; data?: { isForEachEndEdge?: boolean } | undefined;};WorkflowEditorFlowItem
Section titled “WorkflowEditorFlowItem”Shape of a single item in the compiled flow array returned by convertWorkflowEditorToFlow. It matches the shape client.workflows.update() expects, but type is a plain string here, so cast the array to WorkflowUpdateParams.Flow[] (see the example above).
type WorkflowEditorFlowItem = { id: string; type: string; modelId?: string | undefined; inputs?: WorkflowEditorFlowInput[] | undefined; logic?: { transform?: string | undefined; cases?: { condition: string; value: string }[] | undefined; default?: string | undefined; } | undefined; logicType?: 'if-else' | undefined; loopBodyNodeIds?: string[] | undefined; includeOutputsInWorkflowJob?: true | undefined; dependsOn?: string[] | undefined; label?: string | undefined;};WorkflowEditorModel
Section titled “WorkflowEditorModel”Model definition used by the optional getModel resolver in convertWorkflowEditorToFlow. Provides input-type and aspect-ratio resolution hints; return 'unknown' when the model exists but its definition isn’t available.
type WorkflowEditorModel = { id?: string; inputs?: WorkflowEditorModelInput[]; tags?: string[]; uiConfig?: { resolutionComponent?: { widthInput?: string; heightInput?: string; presets?: WorkflowEditorResolutionPreset[]; }; };};
type WorkflowEditorModelInput = { name: string; type: string; inputs?: Array<{ name: string; type: string }>; min?: number; max?: number; step?: number; allowedValues?: string[];};
type WorkflowEditorResolutionPreset = { width?: number; height?: number;};WorkflowEditorConditionBlock
Section titled “WorkflowEditorConditionBlock”Used in ifElse node data. Describes one branch of an if-else condition.
type WorkflowEditorConditionBlock = { conditions: WorkflowEditorCondition[]; logic: 'and' | 'or';};
type WorkflowEditorCondition = { field: string | undefined; operator: string; value?: string | string[];};WorkflowEditorHandleInput / WorkflowEditorHandleOutput
Section titled “WorkflowEditorHandleInput / WorkflowEditorHandleOutput”Handle descriptors attached to node data.inputHandles and data.outputHandles.
type WorkflowEditorHandleInput = { id: string; label?: string; name?: string; type?: string | string[]; subHandles?: WorkflowEditorHandleInput[];};
type WorkflowEditorHandleOutput = { id: string; name?: string; type?: string; isArray?: boolean;};WorkflowEditorFlowInput / WorkflowEditorFlowInputRef
Section titled “WorkflowEditorFlowInput / WorkflowEditorFlowInputRef”Input descriptor inside a WorkflowEditorFlowItem.
type WorkflowEditorFlowInput = { name: string; type: string; ref?: WorkflowEditorFlowInputRef | undefined; value?: string | number | string[] | boolean | undefined; items?: WorkflowEditorFlowInput[][] | undefined; /** Inclusive minimum for number/array inputs. For user-selection, items the user must pick. */ min?: number | undefined; /** Inclusive maximum for number/array inputs. For user-selection, items the user may pick. */ max?: number | undefined;};
type WorkflowEditorFlowInputRef = { equal?: string | undefined; node?: string | undefined; conditional?: string[] | undefined; name?: string | undefined;};normalizeAspectRatio(aspectRatio)
Section titled “normalizeAspectRatio(aspectRatio)”Normalizes an aspect ratio string to the canonical form used by the SDK (reduces by GCD, then maps aliases: '3:7' → '9:21', '7:3' → '21:9').
Signature
normalizeAspectRatio(aspectRatio: string | undefined): string | undefinedReturns the normalized ratio string, or undefined if the input is missing or contains non-positive / non-integer components.
getAspectRatioModelInputNames(model)
Section titled “getAspectRatioModelInputNames(model)”Extracts the width, height, and aspect-ratio input names from a model’s uiConfig or inputs array.
Signature
getAspectRatioModelInputNames( model: WorkflowEditorModel | undefined,): AspectRatioModelInputNamesReturns AspectRatioModelInputNames — { widthInputName?, heightInputName?, aspectRatioInputName? }.
getAspectRatioBounds(model, inputNames)
Section titled “getAspectRatioBounds(model, inputNames)”Returns the min/max width and height bounds for a model, derived from its inputs min/max and uiConfig.resolutionComponent.presets. Falls back to reasonable defaults when those fields are absent.
Signature
getAspectRatioBounds( model: WorkflowEditorModel | undefined, inputNames: Pick<AspectRatioModelInputNames, 'widthInputName' | 'heightInputName'>,): AspectRatioBounds | undefinedReturns AspectRatioBounds | undefined — undefined if model, widthInputName, or heightInputName is absent.
getDimensionsFromAspectRatio(args)
Section titled “getDimensionsFromAspectRatio(args)”Computes pixel dimensions for a given aspect ratio and model bounds. Snaps to the model’s multipleOf step and clamps to min/max.
Signature
getDimensionsFromAspectRatio(args: { aspectRatio: string; bounds: AspectRatioBounds;}): { width: number; height: number } | undefinedReturns { width, height }, or undefined if the aspect ratio string is invalid.
ASPECT_RATIO_PRESETS
Section titled “ASPECT_RATIO_PRESETS”Readonly array of the supported aspect ratio strings, from widest to tallest.
const ASPECT_RATIO_PRESETS = [ '21:9', '16:9', '3:2', '4:3', '5:4', '1:1', '4:5', '3:4', '2:3', '9:16', '9:21',] as const;AspectRatioBounds
Section titled “AspectRatioBounds”type AspectRatioBounds = { minWidth: number; maxWidth: number; minHeight: number; maxHeight: number; multipleOf: number;};AspectRatioModelInputNames
Section titled “AspectRatioModelInputNames”type AspectRatioModelInputNames = { widthInputName?: string | undefined; heightInputName?: string | undefined; aspectRatioInputName?: string | undefined;};