Multi-step pipelines extend the single event_name flow into an ordered sequence of up to 6 events after the initial AI call.
Flow
- Your app calls
POST /v1/ai/asyncon the pipeline workflow (Model tab = initial AI). - ModelRiver auto-advances through configured events:
- Backend steps — webhook + wait for
POST /v1/callback/:channel_id - Callback AI steps — run another workflow's model/settings inline
- Backend steps — webhook + wait for
- Final backend step completes → WebSocket
status: completed
Step data contract
latest_data merge rules
| After | Rule |
|---|---|
| Initial AI | latest_data = ai_response.data |
| Backend callback | latest_data = Map.merge(latest_data, callback.data) |
| Callback AI step | latest_data = target_workflow_ai_response.data (replace) |
customer_data
Set once from the original async request. Never overwritten. Included in every webhook.
Target workflow input
Middle steps with a linked workflow receive a JSON user message:
1{2 "pipeline_input": { "...": "latest_data at this step" },3 "customer_data": { "...": "..." },4 "event_name": "summarize",5 "step_index": 16}Configure target workflows (e.g. ticket_summarizer) to parse pipeline_input.
Hello pipeline example
| Component | Name | Role |
|---|---|---|
| Pipeline workflow | ticket_pipeline | Primary AI triage |
| Helper workflow | ticket_summarizer | Summarize structured output |
| Event 1 | enrich | Backend — enrich ticket data |
| Event 2 | summarize | Callback AI → ticket_summarizer |
| Event 3 | finalize | Backend — final callback before WebSocket |
Webhook envelope (additive fields)
1{2 "callback_url": "https://api.modelriver.com/v1/callback/...",3 "channel_id": "...",4 "data": {5 "type": "task.ai_generated",6 "event": "enrich",7 "channel_id": "...",8 "step_index": 0,9 "total_steps": 3,10 "step_type": "backend",11 "pipeline": { "workflow_id": "...", "workflow_name": "ticket_pipeline", "step_name": "enrich" },12 "ai_response": { "status": "pending", "data": { "...": "latest_data" } },13 "callback_url": "https://api.modelriver.com/v1/callback/...",14 "callback_required": true,15 "customer_data": { "metadata.ticket_id": "123" }16 },17 "timestamp": 176000000018}The step fields are inside the event payload under data; the outer body is the standard webhook envelope (callback_url, channel_id, data, timestamp). The signature covers the raw JSON text of data; see Signature verification. Retries keep the same channel_id and step_index, so deduplicate on that pair.
Callback responses
Intermediate step:
1{2 "status": "step_received",3 "step_index": 0,4 "next_step": "summarize"5}Final step:
1{2 "status": "completed",3 "channel_id": "..."4}WebSocket statuses
| Status | Meaning |
|---|---|
ai_generated | Initial AI complete |
step_ready | Waiting for backend at step N |
step_completed | Backend callback received, advancing |
callback_ai_completed | Linked workflow AI step finished |
completed | Pipeline finished |
Quota
Each production callback_ai step counts toward organization quota (nested under the primary request log via primary_req_id). Test-mode and playground runs are excluded.
Error codes
| Code | When |
|---|---|
step_already_completed | Idempotent duplicate callback (200) |
step_index_mismatch | Callback for wrong step (409) |
pipeline_quota_exceeded | Quota exceeded mid-pipeline (WS) |
step_ai_failed | Callback AI provider error (WS) |
callback_timeout | Backend step timeout (WS) |
Legacy workflows with only event_name and no workflow_events rows continue to work unchanged.
Moving a pipeline
Use Import & export to export a workflow with its structured output and linked target workflows, then import the reviewed bundle into another project.