Workflows and projects
Create, update, share, export, import and run Nodaro workflows from TypeScript with client.workflows, and organize them in projects with client.projects.
client.workflows reads, writes, shares and runs Nodaro workflows, and client.projects manages the projects that hold them. A workflow is a canvas of connected nodes, and every workflow belongs to one project. The methods call the same endpoints as the Workflows REST API. See Workflows and projects for the concepts.
Methods
| Method | What it does |
|---|---|
workflows.list(params) | List the workflows of a project, without their graphs |
workflows.get(id) | Read one workflow with its nodes, connections and settings |
workflows.getPublic(id) | Read a workflow shared by link, without a token |
workflows.create(input) | Create a workflow in a project |
workflows.update(id, input) | Change any fields of a workflow |
workflows.delete(id) | Delete a workflow |
workflows.run(id, params?) | Start a run of the workflow |
workflows.export(workflowId, opts?) | Export a workflow as a portable JSON bundle |
workflows.import(input) | Import a bundle into a project |
workflows.setVisibility(id, visibility) | Make a workflow private or visible to its workspace |
workflows.move(id, params) | Move a workflow to another project |
workflows.sharedWithMe() | List workflows other people shared with you |
workflows.collaborators.* | List, add, change and remove the people a workflow is shared with |
projects.list() and more | List, read, create, update and delete projects |
client.workflows
list(params)
Lists the workflows in a project. The list returns metadata only: nodes, edges, settings and sourcePrompt are left out. Read one workflow with get() for its full graph.
list(params: { projectId: string }): Promise<{ data: Workflow[] }>Prop
Type
const { data: workflows } = await client.workflows.list({ projectId })
for (const wf of workflows) console.log(wf.id, wf.name, wf.updatedAt)Throws NotFoundError when the project is not visible to you.
get(id)
Reads one workflow, including its full nodes, edges and settings.
get(id: string): Promise<{ data: Workflow }>Prop
Type
const { data: wf } = await client.workflows.get(workflowId)
console.log(`${wf.name}: ${wf.nodes?.length ?? 0} nodes, version ${wf.version}`)Keep version if you plan to update the workflow: it makes the update safe against other writers.
getPublic(id)
Reads a workflow that its owner shared by link (GET /v1/public/workflows/:id). No token is needed. The workflow is returned only while its sharing is switched on; otherwise the method throws NotFoundError.
getPublic(id: string): Promise<{ data: Workflow }>Prop
Type
const { data: shared } = await client.workflows.getPublic(workflowId)When the auth provider returns no token, the request is sent without one.
create(input)
Creates a workflow in a project and returns the full record. Only projectId and name are needed; the other fields take server defaults.
create(input: CreateWorkflowInput): Promise<{ data: Workflow }>Prop
Type
const { data: wf } = await client.workflows.create({
projectId,
name: "Product launch video",
nodes: [],
edges: [],
})To get a ready-made graph, export a workflow you built in the editor and read its nodes and edges, or clone a template.
update(id, input)
Changes any subset of a workflow's fields and returns the full updated record (PATCH /v1/workflows/:id). Fields you leave out stay as they are.
update(id: string, input: UpdateWorkflowInput): Promise<{ data: Workflow }>Prop
Type
await client.workflows.update(workflowId, { name: "Renamed", expectedVersion: 7 })Safe updates. Without expectedVersion or expectedUpdatedAt, the last write wins. With one of them, the update applies only if nobody changed the workflow since you read it. Otherwise it throws WorkflowConflictError, whose currentRecord holds the current workflow so you can merge without another read:
import { WorkflowConflictError } from "@nodaro/sdk"
try {
await client.workflows.update(workflowId, { settings, expectedVersion: wf.version })
} catch (err) {
if (err instanceof WorkflowConflictError && err.currentRecord) {
const merged = mergeSettings(err.currentRecord.settings, settings)
await client.workflows.update(workflowId, {
settings: merged,
expectedVersion: err.currentVersion,
})
} else {
throw err
}
}Run-state values on node data, such as the running status or the current job id, are removed by the server and never saved.
delete(id)
Deletes a workflow.
delete(id: string): Promise<{ success: true }>Prop
Type
await client.workflows.delete(workflowId)Throws NotFoundError when the id does not exist or is not yours, so a delete never fails silently.
run(id, params?)
Starts a run of the workflow and returns at once with { executionId, status }. The run continues on the server. Poll client.executions.get() for its progress and result.
run(id: string, params?: { nodeIds?: string[] }): Promise<{ executionId: string; status: "pending" | "running" }>Prop
Type
const { executionId } = await client.workflows.run(workflowId, {
nodeIds: ["text-prompt-1", "image-gen-2"],
})
const { data: execution } = await client.executions.get(executionId)- Throws
InsufficientCreditsErrorwhen the account cannot cover the run's highest possible cost. - An OAuth token needs the
workflows:executescope. - To run a workflow in a workspace, call
runon a client fromclient.withWorkspace().
export(workflowId, opts?)
Exports the saved version of a workflow as a portable JSON bundle, the file format that the editor's Export menu writes. With assets: true, the bundle also carries the assets the workflow uses, like With Assets in the editor.
export(workflowId: string, opts?: { assets?: boolean }): Promise<{ data: WorkflowExport }>Prop
Type
const { data: bundle } = await client.workflows.export(workflowId, { assets: true })Portability. A bundle can only point at media another instance can fetch. When nodes use URLs that other instances cannot reach, such as a self-hosted install's own storage on localhost, a local network address or an .internal name, the bundle lists them under portability.unreachableMedia:
bundle.portability?.unreachableMedia
// [{ nodeId: "n1", nodeLabel: "Video URL", field: "videoUrl", url: "http://localhost:3000/storage/..." }]The field is absent when every media URL is public. Such a bundle still imports, but those nodes cannot run on the other instance until the media is uploaded there again. See Import and export.
import(input)
Imports an exported bundle into one of your projects and returns the new workflow.
import(input: WorkflowExport & { projectId: string }): Promise<{
data: Workflow
importReport?: WorkflowImportReport
}>Prop
Type
const { data: wf, importReport } = await client.workflows.import({ ...bundle, projectId })What the import does:
- Assets are recreated. Characters, objects, creatures and locations in the bundle become new items in your account. The workflow is re-pointed at them, in its asset nodes, in every
@mention in node data, and in the workflow's settings. - Media is copied. Media on other hosts is copied into this instance's storage when it can be reached, so the workflow does not depend on someone else's server. The limits are 25 files for the workflow's media and 25 more for the bundled assets, with images up to 20 MB and video or audio up to 50 MB.
- Copies count against your storage. When your storage runs out, the workflow still lands, and the assets that did not fit are listed in the report.
The importReport says what happened:
importReport
// {
// rehosted: 3, // files copied to this instance
// unreachable: [{ nodeId, nodeLabel, field, url }], // private hosts, left as they were
// skipped: [{ nodeId, field, url, reason: "HTTP 404" }],
// assetIdMap: { "<bundled asset id>": "<new asset id>" },
// assetsSkipped: [{ kind: "character", id, name: "Kira", reason: "Storage limit exceeded" }],
// }assetIdMap is present whenever the bundle carried assets. The server has already updated every mention inside the workflow. Use the map only for references you keep outside it. assetsSkipped appears only when something was left out.
setVisibility(id, visibility)
Makes a workflow "private", visible to its creator and the collaborators you add, or "workspace", visible to everyone in its workspace.
setVisibility(id: string, visibility: "private" | "workspace"): Promise<{ data: Workflow }>Prop
Type
await client.workflows.setVisibility(workflowId, "workspace")Only the creator or a workspace admin may change it; anyone else gets ForbiddenError. Workspaces exist in organizations on Nodaro Cloud.
move(id, params)
Moves a workflow to another project (POST /v1/workflows/:id/move). The workflow leaves its folder.
move(id: string, params: { projectId: string }): Promise<{
data: Workflow
droppedCollaborators: { userId: string; name: string | null }[]
}>Prop
Type
const { droppedCollaborators } = await client.workflows.move(workflowId, { projectId: archiveProjectId })When the move takes the workflow out of a workspace, the access that came from that workspace ends. Those people are returned in droppedCollaborators.
sharedWithMe()
Lists workflows other people shared with you directly. Work in a workspace you belong to is not included, because it already appears in that workspace's own lists. Each workflow carries the grantedRole you hold on it.
sharedWithMe(): Promise<{ data: (Workflow & { grantedRole: "viewer" | "editor" })[] }>const { data: shared } = await client.workflows.sharedWithMe()
for (const wf of shared) console.log(wf.name, wf.grantedRole)The list is empty on instances without organizations.
collaborators
The people a workflow is shared with, reached as client.workflows.collaborators. These methods exist on instances with organizations; elsewhere they throw NotFoundError.
collaborators.list(workflowId: string): Promise<{ data: Collaborator[] }>
collaborators.add(workflowId: string, input: { userId?: string; email?: string; role: "viewer" | "editor" }): Promise<{ data: { userId: string; role: "viewer" | "editor" } }>
collaborators.update(workflowId: string, userId: string, input: { role: "viewer" | "editor" }): Promise<{ data: { userId: string; role: "viewer" | "editor" } }>
collaborators.remove(workflowId: string, userId: string): Promise<{ success: true }>Prop
Type
await client.workflows.collaborators.add(workflowId, { email: "editor@example.com", role: "editor" })
const { data: people } = await client.workflows.collaborators.list(workflowId)
// [{ userId, name, avatar, role }]: email addresses are never returnedA collaborator can call remove() with their own user id to leave a workflow.
client.projects
A project groups workflows. Every workflow belongs to exactly one project.
projects.list()
Lists your projects.
list(): Promise<{ data: Project[] }>const { data: projects } = await client.projects.list()projects.get(id)
Reads one project.
get(id: string): Promise<{ data: Project }>Prop
Type
const { data: project } = await client.projects.get(projectId)projects.create(input)
Creates a project.
create(input: { name: string; description?: string; settings?: Record<string, unknown> }): Promise<{ data: Project }>Prop
Type
const { data: project } = await client.projects.create({ name: "Spring campaign" })projects.update(id, input)
Changes a project. Pass at least one field.
update(id: string, input: { name?: string; description?: string; settings?: Record<string, unknown> }): Promise<{ data: Project }>Prop
Type
await client.projects.update(projectId, { description: "Assets for the spring launch" })projects.delete(id)
Deletes a project.
delete(id: string): Promise<{ success: true }>Prop
Type
await client.projects.delete(projectId)Types
| Type | Shape |
|---|---|
Workflow | id, projectId, userId, name, description?, folderId?, version?, thumbnailUrl?, nodes?, edges?, settings?, sourcePrompt?, createdAt, updatedAt. The graph fields are present on full records only. |
Project | id, userId, name, description?, settings?, createdAt, updatedAt |
Collaborator | userId, name?, avatar?, role |
WorkflowExport | The portable bundle that export() returns and import() accepts |
GenericNode, GenericEdge | The node and connection shapes used in nodes and edges |
All of them are exported from @nodaro/sdk. See Types.
Frequently asked questions
Related
Jobs and executions
Run nodes
Workflows
Workflows and projects
Import and export
Last updated on
Errors
Every error the Nodaro TypeScript SDK throws, with its HTTP status, code and fields, and what to do about credits, rate limits, conflicts and failed jobs.
Jobs and executions
Poll, list, cancel and delete Nodaro runs from TypeScript. client.jobs tracks single generations, and client.executions tracks whole workflow runs.