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 useinput— 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 type | Referenceable fields |
|---|---|
| Text generation / speech-to-text (STT) | text |
| Image analysis / document analysis | content |
| Text-to-speech (TTS) | audio_url |
| Image generation | image_url, image_urls, fileKeys |
| Video generation | video_url, fileName |
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)
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 type | When it arrives | Information included |
|---|---|---|
block_start | When a block starts | index, block_id, model |
block_done | When a block finishes | output, charge, total_charge |
block_error | When a block fails | block_id, error |
done | When the entire workflow finishes | run_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"
}
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
- Detailed spec for each endpoint → API Reference
- Model types and uses → Models guide
- Error code explanations → Error guide