Skip to main content

Workflows

A workflow is a feature that chains several AI tasks in sequence and runs them all at once.

For example, like "① write a draft → ② convert that draft into speech," a later task can pick up and use the result of an earlier task. Instead of calling each one separately, it's all handled in a single request.

Each step is called a block. The blocks run in order from top to bottom (a linear chain). If any one of them fails midway, it stops there.


Creating a block

Each block is made up of the following three things.

{
"id": "draft",
"model": "modoo-text",
"input": { "prompt": "Write promotional copy for a new cafe" }
}
  • id — the name of this block (used to refer to its result from later blocks)
  • model — the AI model to use
  • input — the input value to feed this block

Picking up the result of an earlier block

In a later block, you can pull in an earlier block's result using the form {{block_id.field}}.

For example, to have the next block turn the text (text) created by the draft block into speech, you write it like this.

[
{
"id": "draft",
"model": "modoo-text",
"input": { "prompt": "Write promotional copy for a new cafe" }
},
{
"id": "voice",
"model": "modoo-tts-minimax",
"input": { "text": "{{draft.text}}" }
}
]

Here, {{draft.text}} means "put the text created by the draft block here."

Referenceable result fields by block type

Each block has a defined set of result fields that later blocks can reference.

Block typeReferenceable fields
Text generation / speech-to-text (STT)text
Image analysis / document analysiscontent
Text-to-speech (TTS)audio_url
Image generationimage_url, image_urls, fileKeys
Video generationvideo_url, fileName
Audio and video only at the final step

Audio and video results do not flow into the next block's input (they're terminal). So place a block that creates audio or video as the final step of the workflow.


Compatibility validation before running (rest assured)

Before running a workflow, the system checks in advance whether the block connections are valid. It's a safeguard that prevents you from wasting credits on incorrect connections.

In the following cases, it won't start running and instead tells you the reason, kindly, along with a 400 invalid_workflow error.

  • When you try to pull the result of a block that hasn't run yet (one that comes later)
  • When you point to a result field that doesn't exist
  • When you attempt a connection between mismatched kinds (e.g. feeding an audio result into a text input)
Embedding inside a sentence is always OK

Cases where you embed a result inside a sentence, like "Please refine this draft:\n{{draft.text}}", are always allowed as text. Use them without worry.


Billing

In a workflow, credits are deducted separately for each block. The total_charge in the response is the overall total, and each result's charge is the amount deducted for that block.


Endpoints

The base URL is https://modoo.devdive.me. Every request requires the authentication header.

Authorization: Bearer sk-modoo-...

POST /v1/workflows/runs — Run and save the result (recommended)

Runs the workflow and saves the result. You can look it up again later, so this is the most recommended approach.

# This request: runs the workflow and saves the result.
curl https://modoo.devdive.me/v1/workflows/runs \
-H "Authorization: Bearer sk-modoo-..." \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{ "id": "draft", "model": "modoo-text", "input": { "prompt": "Cafe promotional copy" } },
{ "id": "voice", "model": "modoo-tts-minimax", "input": { "text": "{{draft.text}}" } }
]
}'

Example response — you can look it up again later with run_id, and total_charge is the total credits deducted.

{
"run_id": "run_abc123",
"status": "succeeded",
"total_charge": 0.15,
"results": [
{ "id": "draft", "model": "modoo-text", "output": { "text": "Now open!" }, "charge": 0.05 },
{ "id": "voice", "model": "modoo-tts-minimax", "output": { "audio_url": "https://.../voice.mp3" }, "charge": 0.10 }
]
}

POST /v1/workflows/run — Just run and get the result immediately (not saved)

Returns the result directly in the response only, without saving it. Handy for one-off use.

# This request: runs the workflow and returns the result immediately (not saved).
curl https://modoo.devdive.me/v1/workflows/run \
-H "Authorization: Bearer sk-modoo-..." \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{ "id": "draft", "model": "modoo-text", "input": { "prompt": "Cafe promotional copy" } }
]
}'

Example response

{
"results": [
{ "id": "draft", "model": "modoo-text", "output": { "text": "Now open!" }, "charge": 0.05 }
],
"total_charge": 0.05
}

POST /v1/workflows/stream — Watch per-block progress in real time

Tells you the status in real time as each block progresses, one at a time. Great for showing the progress of a long workflow on screen. (This run is saved.)

This approach is also SSE (Server-Sent Events), so the server keeps sending lines in the form data: .... Each line includes a type that tells you what happened.

Event typeWhen it arrivesInformation included
block_startWhen a block startsindex, block_id, model
block_doneWhen a block finishesoutput, charge, total_charge
block_errorWhen a block failsblock_id, error
doneWhen the entire workflow finishesrun_id, status, total_charge, results, error
# This request: receives per-block progress in real time.
curl https://modoo.devdive.me/v1/workflows/stream \
-H "Authorization: Bearer sk-modoo-..." \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{ "id": "draft", "model": "modoo-text", "input": { "prompt": "Cafe promotional copy" } },
{ "id": "voice", "model": "modoo-tts-minimax", "input": { "text": "{{draft.text}}" } }
]
}'

The response arrives as several lines in order, like this.

data: {"type": "block_start", "index": 0, "block_id": "draft", "model": "modoo-text"}
data: {"type": "block_done", "output": {"text": "Now open!"}, "charge": 0.05, "total_charge": 0.05}
data: {"type": "block_start", "index": 1, "block_id": "voice", "model": "modoo-tts-minimax"}
data: {"type": "block_done", "output": {"audio_url": "https://.../voice.mp3"}, "charge": 0.10, "total_charge": 0.15}
data: {"type": "done", "run_id": "run_abc123", "status": "succeeded", "total_charge": 0.15, "results": [...], "error": null}

GET /v1/workflows/runs/{run_id} — View a saved run result again

Looks up a previously saved workflow run result by run_id.

# This request: fetches a saved workflow run result again.
curl https://modoo.devdive.me/v1/workflows/runs/run_abc123 \
-H "Authorization: Bearer sk-modoo-..."

Example response

{
"run_id": "run_abc123",
"status": "succeeded",
"total_charge": 0.15,
"results": [
{ "id": "draft", "model": "modoo-text", "output": { "text": "Now open!" }, "charge": 0.05 },
{ "id": "voice", "model": "modoo-tts-minimax", "output": { "audio_url": "https://.../voice.mp3" }, "charge": 0.10 }
],
"error": null,
"created_at": "2026-07-01T09:00:00Z"
}
Don't save media URLs

The audio/image/video URLs contained in the result expire over time. Don't keep the URLs for long — look them up again with this endpoint when you need them and use the latest URL.


What's next