Workflows

Chain multiple Botverse tools into a single declarative pipeline. Define your steps in JSON, submit once, and poll for results — the engine handles parallelism, dependencies, retries, and failure recovery.

How it works

A workflow is a JSON document written in BWDL — the Botverse Workflow Definition Language. You submit it with submit_workflow, which validates the definition, charges a billing gate, and immediately returns a workflow_id. Then you poll get_workflow_statusevery 5–10 seconds: each call advances the workflow — it checks in-progress steps, dispatches newly ready ones, and applies failure policies. When a terminal status is returned, the workflow is done.

submit_workflow(definition) → workflow_id
↓ poll every 5–10 s
get_workflow_status(workflow_id) → status + step states
↓ repeat until terminal
COMPLETED | PARTIALLY_FAILED | FAILED | CANCELLED
Workflows require auto-refill to be enabled on your wallet and a saved payment card. This prevents a mid-workflow balance failure from leaving jobs stranded. Enable it at Dashboard → Billing.

Quick start

The simplest possible workflow — one step, no dependencies. Once you understand the submit-and-poll pattern, multi-step pipelines work exactly the same way.

1. Submit a workflow
// Call submit_workflow with your BWDL definition
const result = await callTool("submit_workflow", {
  definition: {
    workflow_id: "my-first-workflow-001",
    steps: [
      {
        id: "transcode",
        tool: "transcode_from_url",
        inputs: {
          source_url: "https://example.com/video.mp4",
          output_format: "mp4"
        }
      }
    ]
  }
});

// Returns immediately
// { workflow_id: "my-first-workflow-001", status: "PROCESSING", step_count: 1 }
2. Poll until terminal
let status;
do {
  await sleep(5000);
  status = await callTool("get_workflow_status", {
    workflow_id: "my-first-workflow-001"
  });
} while (!["COMPLETED", "FAILED", "PARTIALLY_FAILED", "CANCELLED"].includes(status.status));

// Terminal — inspect step outputs
const step = status.steps.find(s => s.step_id === "transcode");
console.log(step.status);      // "COMPLETED"
console.log(step.output_url);  // pre-signed download URL
console.log(step.cost_usd);    // e.g. 0.001

BWDL schema reference

A BWDL document is a JSON object. The $schema field is optional but enables validation in editors that support JSON Schema.

Top-level fields

FieldTypeReqDescription
$schemastringnoSet to https://botverse.cloud/schemas/workflow/v1.json to enable editor validation
workflow_idstringyesUnique identifier. Submitting the same workflow_id twice returns the existing workflow (idempotent)
versionstringnoWorkflow definition version — use "1.0"
descriptionstringnoHuman-readable summary shown in the dashboard
paramsobjectnoInput values accessible in step inputs as $.params.{key}
stepsarrayyesOne or more step objects. Must not be empty

Step fields

FieldTypeReqDescription
idstringyesUnique step ID within this workflow. Used in depends_on and expression references
toolstringyesThe Botverse tool to run. See Available tools below
inputsobjectyesTool arguments. Values may be literals or $.path expressions
descriptionstringnoHuman-readable label shown in status responses
depends_onstring[]noStep IDs that must reach COMPLETED before this step dispatches. Omit to run immediately
whenstringnoCondition expression. Step is SKIPPED if this evaluates false — see Conditional steps
failure_modestringnoWhat to do when this step fails. Default: HALT — see Failure modes
on_failure_compensatestringnoStep ID to run as compensation. Required when failure_mode is COMPENSATE
retryobjectnoRetry policy — see Retry

Available tools

The tool field must be one of these values:

ToolCategoryKey inputsKey outputs
transcode_from_urlTranscodesource_url, output_formatoutput_key, output_url
transcode_videoTranscodeobject_key, output_formatoutput_key, output_url
convert_from_urlConvertsource_url, output_formatoutput_key, output_url
convert_fileConvertobject_key, output_formatoutput_key, output_url
convert_contentConvertcontent, input_format, output_formatoutput_key, output_url

Transcode output_format values: mp4 webm mov_prores mp3 gif. Convert output_format values: docx pdf html txt md rst xlsx.

Expression syntax

Step inputs support JSONPath-style expressions that are resolved at dispatch time. Any input value that starts with $. is treated as an expression; all other values are used as-is.

$.params.keyValue from the workflow's params object
$.steps.{id}.output_keyS3 key of the step's output — auto-resolves to a pre-signed download URL when used as source_url
$.steps.{id}.output_urlPre-signed download URL for the step's output
$.steps.{id}.statusCurrent status string of a step — useful in when conditions
When you use $.steps.{id}.output_key as the value of a source_url input, the engine automatically substitutes the pre-signed download URL for that step — you don't need to handle URL generation yourself.

Parallelism and dependencies

Steps with no depends_on (or an empty array) are dispatched immediately when the workflow starts. Steps that list one or more dependencies wait until every listed step reaches COMPLETED before they dispatch. This makes fan-out and fan-in natural to express.

ingest (runs first — no depends_on)
├── mp4 depends_on: ["ingest"] ─┐
├── webm depends_on: ["ingest"] ─┤ parallel fan-out
└── audio depends_on: ["ingest"] ─┘

A step can depend on multiple steps — it will wait until all of them complete (fan-in). Circular dependencies are detected at submit time and rejected with a CIRCULAR_DEPENDENCY error.

Conditional steps

A step with a when field is only dispatched if the condition evaluates to true. If it evaluates to false, the step is marked SKIPPED — it does not block downstream steps that depend on it.

Supported condition syntax:

$.steps.{id}.status == 'COMPLETED'Run only if the referenced step completed successfully
$.steps.{id}.status != 'FAILED'Skip only if the referenced step failed
$.params.{key} == 'value'Conditional based on a workflow parameter
Example — conditional step
{
  "id": "prores_master",
  "tool": "transcode_from_url",
  "depends_on": ["ingest"],
  "when": "$.params.include_prores == 'true'",
  "inputs": {
    "source_url": "$.steps.ingest.output_key",
    "output_format": "mov_prores"
  }
}

Failure modes

Each step can declare how the workflow behaves if that step fails. The default is HALT.

ModeOn failure
HALTCancel all remaining queued steps. Workflow terminates as FAILED.
CONTINUELet all other steps proceed regardless. Workflow may end as PARTIALLY_FAILED.
SKIP_DEPENDENTSSkip all steps that transitively depend on this step. Independent branches continue. Workflow may end as PARTIALLY_FAILED.
COMPENSATERun the step named in on_failure_compensate as a clean-up action, then HALT. Useful for teardown logic.
COMPENSATE example
{
  "id": "upload_to_cdn",
  "tool": "transcode_from_url",
  "failure_mode": "COMPENSATE",
  "on_failure_compensate": "cleanup",
  "inputs": { "source_url": "$.params.source_url", "output_format": "mp4" }
},
{
  "id": "cleanup",
  "tool": "convert_content",
  "inputs": {
    "content": "Cleanup triggered for $.params.job_ref",
    "input_format": "txt",
    "output_format": "txt"
  }
}

Retry policy

retry object
"retry": {
  "max_attempts": 3,            // 1–10. Counts the original attempt — so 3 = 2 retries.
  "initial_interval_seconds": 5,// Wait before first retry. Default: 1.
  "backoff_coefficient": 2.0,   // Multiply interval on each retry. Default: 1 (no backoff).
  "retryable_errors": [         // Optional — only retry these error substrings.
    "FETCH_FAILED",
    "TIMEOUT"
  ]
}

Status values

Workflow statuses

PROCESSINGin progressSteps are running
COMPLETEDterminalAll steps completed successfully
PARTIALLY_FAILEDterminalOne or more steps failed but the workflow continued (CONTINUE or SKIP_DEPENDENTS mode)
FAILEDterminalA HALT or COMPENSATE failure terminated the workflow
CANCELLEDterminalcancel_workflow was called

Step statuses

QUEUEDWaiting for dependencies to complete
DISPATCHEDJob submitted — awaiting completion
COMPLETEDJob finished successfully. output_url is available.
FAILEDJob failed. error_message explains why.
SKIPPEDwhen condition was false, or upstream step was SKIP_DEPENDENTS'd
CANCELLEDWorkflow was cancelled or a HALT was triggered

MCP tool reference

submit_workflow

Validate and submit a BWDL workflow definition. Returns immediately — the workflow runs server-side. Submitting the same workflow_id twice is idempotent: the existing workflow is returned without re-running.

Parameters
NameTypeReqDescription
definitionobjectyesBWDL workflow definition. Must include workflow_id (string) and steps (array). Each step requires id, tool, and inputs.
Response fields
FieldTypeDescription
workflow_idstringThe workflow_id from your definition. Pass this to get_workflow_status.
statusstringInitial status — always PROCESSING on a new submission.
step_countnumberNumber of steps in the workflow.
already_existsbooleantrue if this workflow_id was already submitted.
get_workflow_status

Advance and return the current state of a workflow. Each call checks in-progress steps, dispatches newly ready steps, and applies failure policies. Poll every 5–10 seconds until a terminal status is returned.

Parameters
NameTypeReqDescription
workflow_idstringyesWorkflow ID returned by submit_workflow.
Response fields
FieldTypeDescription
workflow_idstringEchoed back.
statusstringCurrent workflow status.
total_cost_usdnumberCumulative cost of all completed steps.
error_messagestringSet when the workflow reaches FAILED.
completed_atstringISO timestamp when the workflow reached a terminal state.
stepsarrayArray of step state objects — see fields below.

Each object in steps contains:

step_idstringMatches the id field in your definition
statusstringQUEUED | DISPATCHED | COMPLETED | FAILED | SKIPPED | CANCELLED
output_urlstringPre-signed download URL. Available when status is COMPLETED.
cost_usdnumberCost charged for this step.
error_messagestringFailure reason. Set when status is FAILED.
dispatched_atstringISO timestamp when the step was dispatched.
completed_atstringISO timestamp when the step reached a terminal state.
cancel_workflow

Cancel an in-progress workflow. All QUEUED and DISPATCHED steps are immediately marked CANCELLED. Has no effect on already-terminal workflows — calling it on a COMPLETED or FAILED workflow returns an error.

Parameters
NameTypeReqDescription
workflow_idstringyesWorkflow ID to cancel.
Response fields
FieldTypeDescription
workflow_idstringEchoed back.
cancelledbooleanAlways true on success.
statusstringAlways CANCELLED on success.

Error codes

Validation errors from submit_workflow include a machine-readable codein the JSON-RPC error object at error.data.code.

INVALID_WORKFLOW_SCHEMAMissing required field, empty steps array, or duplicate step ID
INVALID_TOOLStep references a tool name that doesn't exist
INVALID_OUTPUT_FORMAToutput_format value not supported for the chosen tool
INVALID_DEPENDENCYdepends_on or on_failure_compensate references a step ID that doesn't exist
CIRCULAR_DEPENDENCYThe dependency graph contains a cycle
WORKFLOW_REQUIRES_AUTO_REFILLAuto-refill is not enabled on your wallet
WORKFLOW_NOT_FOUNDThe workflow_id doesn't exist or doesn't belong to your account
WORKFLOW_ALREADY_TERMINALcancel_workflow called on a workflow that has already completed or failed

Complete example — multi-rendition pipeline

This pipeline takes a source URL from params, ingests it as an MP4, then fans out to three parallel renditions: H.264 MP4 for web, VP9 WebM for modern browsers, and an MP3 audio track. The three output steps all depend on the ingest step and run concurrently.

ingest (transcode_from_url — fetches source URL)
├── mp4 parallel
├── webm parallel
└── audio parallel
multi-rendition-pipeline.json
{
  "$schema": "https://botverse.cloud/schemas/workflow/v1.json",
  "workflow_id": "multi-rendition-20240610-001",
  "version": "1.0",
  "description": "Transcode one source URL into MP4, WebM, and MP3",
  "params": {
    "source_url": "https://example.com/video.mp4"
  },
  "steps": [
    {
      "id": "ingest",
      "tool": "transcode_from_url",
      "description": "Fetch and store the source file as baseline MP4",
      "inputs": {
        "source_url": "$.params.source_url",
        "output_format": "mp4"
      }
    },
    {
      "id": "mp4",
      "tool": "transcode_from_url",
      "description": "H.264 MP4 for web delivery",
      "depends_on": ["ingest"],
      "inputs": {
        "source_url": "$.steps.ingest.output_key",
        "output_format": "mp4"
      },
      "retry": { "max_attempts": 2 }
    },
    {
      "id": "webm",
      "tool": "transcode_from_url",
      "description": "VP9 WebM for modern browsers",
      "depends_on": ["ingest"],
      "failure_mode": "SKIP_DEPENDENTS",
      "inputs": {
        "source_url": "$.steps.ingest.output_key",
        "output_format": "webm"
      }
    },
    {
      "id": "audio",
      "tool": "transcode_from_url",
      "description": "MP3 audio track",
      "depends_on": ["ingest"],
      "failure_mode": "CONTINUE",
      "inputs": {
        "source_url": "$.steps.ingest.output_key",
        "output_format": "mp3"
      }
    }
  ]
}

Submitting and polling this workflow

// Submit
const { workflow_id } = await callTool("submit_workflow", { definition });

// Poll
let result;
do {
  await new Promise(r => setTimeout(r, 5000));
  result = await callTool("get_workflow_status", { workflow_id });
} while (!["COMPLETED","FAILED","PARTIALLY_FAILED","CANCELLED"].includes(result.status));

// Collect outputs
for (const step of result.steps) {
  console.log(step.step_id, step.status, step.output_url);
}
// mp4   COMPLETED  https://storage.botverse.cloud/...?X-Amz-...
// webm  COMPLETED  https://storage.botverse.cloud/...?X-Amz-...
// audio COMPLETED  https://storage.botverse.cloud/...?X-Amz-...
Pre-signed download URLs in output_url are valid for 1 hour. Download and store the files to long-term storage before the URL expires.