Skip to content
Get started
TypeScript

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.


Available on response.workflow after calling client.workflows.retrieve().

Signature

response.workflow.findNode(nodeId: string): Flow | undefined

Parameters

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_...'
}

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}`);
});

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(): void

Parameters

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);
}

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);

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}`);
});
});

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[]): void

Parameters

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): WorkflowExportData

Parameters

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);
}
}

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.


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[] });

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[];
};

type WorkflowEditorEdge = {
source: string;
target: string;
sourceHandle?: string | null;
targetHandle?: string | null;
data?: { isForEachEndEdge?: boolean } | undefined;
};

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;
};

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;
};

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;
};

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 | undefined

Returns the normalized ratio string, or undefined if the input is missing or contains non-positive / non-integer components.


Extracts the width, height, and aspect-ratio input names from a model’s uiConfig or inputs array.

Signature

getAspectRatioModelInputNames(
model: WorkflowEditorModel | undefined,
): AspectRatioModelInputNames

Returns AspectRatioModelInputNames — { widthInputName?, heightInputName?, aspectRatioInputName? }.


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 | undefined

Returns AspectRatioBounds | undefined — undefined if model, widthInputName, or heightInputName is absent.


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 } | undefined

Returns { width, height }, or undefined if the aspect ratio string is invalid.


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;

type AspectRatioBounds = {
minWidth: number;
maxWidth: number;
minHeight: number;
maxHeight: number;
multipleOf: number;
};

type AspectRatioModelInputNames = {
widthInputName?: string | undefined;
heightInputName?: string | undefined;
aspectRatioInputName?: string | undefined;
};