Skip to main content

Running Workflows Through the API

The Moco service exposes the same workflow operations through two transports:

  • a REST / OpenAPI surface under /api, for ordinary application code
  • an MCP endpoint at /mcp, for AI agents and for the moco CLI

Both are served by the same process, authenticate the same way, and are backed by the same engine. Which you reach for depends on the caller: a service integration wants REST, an LLM agent wants MCP.

For the conceptual model behind the options field used throughout this guide — execute modes, entity workflows, retry policies — see How Workflows Run.


Authentication​

Every endpoint requires an Authorization header. Two credential types are accepted:

Authorization: Bearer <oauth2-jwt>
Authorization: ApiKey <moco-api-key>

API keys may also be sent as Bearer <moco-api-key> — the server recognizes the key format regardless of scheme, which matters because the MCP transport only accepts Bearer.

Create an API key with the CLI:

moco apikey create my-service --expires-in 90d

A request with a missing, malformed, or expired credential gets 401 with a WWW-Authenticate: Bearer header. On a deployment with MOCO_AUTH_ENABLED turned off, requests resolve to an anonymous caller instead.


OpenAPI​

The service publishes its own schema and interactive documentation:

PathContents
/api/openapi.jsonOpenAPI 3 schema — feed this to your client generator
/api/docsSwagger UI
/api/redocReDoc

Generating a client from /api/openapi.json is the recommended integration path; the tables below describe the endpoints you will get.

Workflow endpoints​

MethodPathPurpose
POST/api/workflow/executeRun a workflow and wait for the result
POST/api/workflow/startStart a workflow, return its workflow_id
POST/api/workflow/cancelCancel gracefully (the workflow can run cleanup)
POST/api/workflow/terminateTerminate forcefully (no cleanup)
GET/api/workflow/status/{workflow_id}Current status and result or error
GET/api/workflow/history/{workflow_id}Chronological execution history
GET/api/workflow/executions?wfspec_name=…List runs for a wfspec

Executing a deployed workflow​

curl -X POST https://www.my-moco.com/api/workflow/execute \
-H "Authorization: ApiKey $MOCO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"wfspec_info": { "name": "hello-moco", "version": "1.0.0" },
"input_data": { "owner": "python", "repo": "cpython" },
"options": { "tier": "prod" }
}'

The response body is the workflow's result — what its output evaluated to — encoded as JSON.

wfspec_info identifies the workflow three ways:

FieldMeaning
nameA deployed wfspec name, resolved through the deployment for the caller and tier
versionOptional. Omit it to take the version the stage resolves to
contentThe wfspec YAML itself, as a string — or an array of strings for a multi-file bundle

Executing an undeployed spec​

Pass the YAML directly in content. Nothing needs to be published, which is how the CLI runs local files and how dynamically generated workflows are executed:

curl -X POST https://www.my-moco.com/api/workflow/execute \
-H "Authorization: ApiKey $MOCO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"wfspec_info": {
"content": "header:\n schema_version: 2.0.0\n workflow_name: adhoc\noutput: \"{{ answer }}\"\nmain:\n set:\n - answer: \"{{ 6 * 7 }}\"\n"
}
}'

For a bundle, pass content as an array — the first element is the entry point and the rest are child workflows it may reference by ref, using their workflow_name.

Starting and managing a long run​

# start
curl -X POST https://www.my-moco.com/api/workflow/start \
-H "Authorization: ApiKey $MOCO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"wfspec_info": {"name": "order-monitor"}, "input_data": {"order_id": "ORD-1"}}'
# -> {"workflow_id": "order-monitor:1.0.0:alice:6f1c…", "status": "started"}

# poll
curl -H "Authorization: ApiKey $MOCO_API_KEY" \
"https://www.my-moco.com/api/workflow/status/order-monitor:1.0.0:alice:6f1c…"

# stop
curl -X POST https://www.my-moco.com/api/workflow/cancel \
-H "Authorization: ApiKey $MOCO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"workflow_id": "order-monitor:1.0.0:alice:6f1c…"}'

A generated ID has the form {workflow_name}:{version}:{user_id}:{trace_id}, where version is the package version, so a run is identifiable at a glance. Supply your own workflow_id in options when you need a specific address — see entity workflows.

status reports running, completed, failed, cancelled, terminated, or unknown, along with start_time, close_time, and the result or error.

These four operations need a durable handle, so they are unavailable under execute_mode: "in-memory" — that combination returns 400 rather than failing silently.

Activity endpoints​

The same shape exists for running a single activity outside any workflow — useful for smoke-testing an integration or reusing Moco's connectors without authoring a spec:

MethodPathPurpose
POST/api/activity/executeRun one activity and wait
POST/api/activity/startStart one activity, return its activity_id
GET/api/activity/result/{activity_id}Status and output_data
POST/api/activity/cancelCancel gracefully
POST/api/activity/terminateTerminate forcefully
curl -X POST https://www.my-moco.com/api/activity/execute \
-H "Authorization: ApiKey $MOCO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"activity_type": "http.request",
"input_data": { "method": "GET", "url": "https://api.github.com" },
"options": { "execute_mode": "in-memory" }
}'

Error responses​

StatusMeaning
200Success
400Invalid request — an unrecognized execute_mode, or an async operation on a runtime that cannot support it
401Missing, malformed, expired, or revoked credential
500Execution failed. The body carries the error detail

A workflow that stops early with raise type: terminate is a successful HTTP call — the terminate_message is reported in the result, not the status code.


MCP​

The MCP endpoint is mounted at /mcp and speaks streamable HTTP. It exists so that AI agents can run Moco workflows as tools, and it is what the moco CLI itself uses.

Authenticate with the same credentials, always using the Bearer scheme:

https://www.my-moco.com/mcp
Authorization: Bearer <token-or-api-key>

Tools​

ToolArgumentsPurpose
execute_workflowwfspec_info, input_data, optionsRun synchronously, return the result
start_workflowwfspec_info, input_data, optionsStart, return workflow_id
cancel_workflowworkflow_idCancel gracefully
terminate_workflowworkflow_id, reasonTerminate forcefully
get_workflow_statusworkflow_idStatus, times, result or error
get_workflow_historyworkflow_idChronological event list
list_executionswfspec_name, wfspec_version, max_resultsRuns for a wfspec
execute_activityactivity_type, input_data, config_data, optionsRun one activity
start_activityactivity_type, input_data, config_data, optionsStart one activity
get_activity_resultactivity_idActivity status and output
cancel_activity / terminate_activityactivity_idStop an activity

Arguments mirror the REST bodies exactly. Every tool returns a JSON string with a success flag — failures come back as {"success": false, "error": …, "error_type": …} rather than as a protocol error, so an agent can read and reason about them.

Streaming progress​

execute_workflow is the one tool that streams. While the workflow runs, it reports MCP progress notifications, each carrying a serialized debug event: task boundaries, activity dispatches, and the value of any variable marked with the # modifier. This is what produces the live output you see from moco run, and it lets an agent narrate a long workflow instead of blocking silently.

Streaming requires the server to have a debug event bus configured (MOCO_RMQ_CONNECTION_URL). When it is absent the tool still works — it simply returns the final result with no intermediate notifications.

Registering Moco with an MCP client​

{
"mcpServers": {
"moco": {
"type": "http",
"url": "https://www.my-moco.com/mcp",
"headers": { "Authorization": "Bearer ${MOCO_API_KEY}" }
}
}
}

An agent connected this way can discover your deployed workflows and call them as tools — which is the point of publishing a workflow rather than keeping it in a file.


execute_options​

options is the third argument of every execute/start call, on both transports. It configures how the run is dispatched, never what it does — the workflow's own inputs go in input_data.

OptionTypePurpose
execute_mode"workflow" | "standalone-activity" | "in-memory"How the run is dispatched. Default "workflow"
tierstringExecution stage — dev, beta, prod. Selects which deployed version a name resolves to
workflow_idstringCaller-supplied ID. Generated when omitted; required for entity workflows
trace_idstringCorrelation ID shared by the run and all its children. Generated when omitted
debug_modeboolEmit debug events, including #-marked variables
debug_infoanyArbitrary payload echoed into debug events
enable_otel_traceboolEmit OpenTelemetry spans for the run
catch_exceptionboolReturn the error as a result instead of failing the run
retry_policyobjectTimeout and retry configuration for the run
entity_workflow_argsobjectEntity-workflow / signal-with-start configuration

execute_mode​

{ "options": { "execute_mode": "standalone-activity" } }
  • "workflow" (default) — a durable Temporal workflow; every activity is dispatched and recorded separately, so the run survives worker restarts.
  • "standalone-activity" — the whole wfspec runs as a single Temporal activity. Durable at the top level only, but it avoids per-step marshalling and the activity payload size limit, so it suits large-data and CPU-bound workflows.
  • "in-memory" — runs in the server process with no Temporal involvement. Lowest latency, no durability, and no start / cancel / terminate / status.

The trade-offs are covered in How Workflows Run.

retry_policy​

{
"options": {
"retry_policy": {
"timeout_sec": 600,
"max_attempts": 3,
"initial_interval_sec": 1,
"backoff_coefficient": 2.0,
"maximum_interval_sec": 60,
"non_retryable_error_types": ["ValidationError"]
}
}
}

For a workflow run, timeout_sec is the overall run timeout — 24 hours when unset. Workflows default to a single attempt: Moco does not retry a whole workflow unless you ask it to, because activity-level retries are usually what you want. Only the Temporal runtime honors these settings; the in-memory runtime ignores them.

Values here are plain JSON numbers: the API does not evaluate {{ }} expressions in run options. To compute a policy from the run's data, write it in the spec instead — see Computed retry policies.

Under execute_mode: "standalone-activity" the same object also carries heartbeat_timeout_sec and heartbeat_interval_sec, which default to 60 s and 20 s so Temporal can detect a dead worker.

entity_workflow_args​

Addresses a long-lived workflow instance by ID, starting it if it is not already running and delivering an event either way:

{
"options": {
"workflow_id": "order-entity:ORD-001",
"entity_workflow_args": {
"is_entity_workflow": true,
"signal_name": "order_events",
"signal_input": {
"event_type": "add_item",
"data": { "sku": "WIDGET-A", "price": 10 }
}
}
}
}

workflow_id must be set explicitly — it is the entity's address. signal_name is the topic the workflow's state machine listens on, and signal_input is the event delivered atomically with the start. See How Workflows Run.

trace_id and workflow_id​

Both are generated if you omit them. Supply them when you need to correlate a Moco run with a request in your own system:

  • trace_id is shared by the workflow and every child it spawns — the key to use when searching logs or traces for one logical operation.
  • workflow_id is unique per instance and is the handle for status, cancel, and terminate. Supplying a deterministic one also gives you idempotency: starting twice with the same ID is rejected by Temporal unless the run is an entity workflow.

Next steps​