Use the Speak AI MCP server tool validate_automation_graph to check a step graph
without saving anything.
What does it do?
Check a step graph without saving anything. Reports the same problems
create_automation and update_automation would refuse (branch wiring, rules a
condition cannot actually read, shapes the Speak web editor could not reopen) plus
non-blocking warnings. Use it to iterate on a branched automation instead of
discovering the problems one failed save at a time.
Parameters
validate_automation_graph takes 3 parameters, 1 of them required.
| Name | Type | Required | Description |
|---|---|---|---|
steps |
array of object | Yes | Ordered array of graph steps (1-20). Each step is an object: { stepId: string (unique within the array), stepType: one of “speak-upload” | “magic-prompt” | “translation” | “filter” | “condition” | “notify” | “outbound-webhook” | “composio-action”, dependsOn?: string[] (stepIds this step runs after), branch?: “true”|“false” (which outcome of an upstream condition step this step belongs to) } plus ONE config key matching stepType: • speak-upload -> speakUpload: { sourceMode: “url”|“file”, sourceUrl (required when sourceMode=“url”; tokens allowed. If the token resolves to an object, the first http(s) URL inside it is used), folderId (required, unless folderRouting.mode=“dynamic” where it becomes the optional fallback), name? (tokens allowed, mixable with static text), language? (language code or token), fieldsMap?: { <customFieldId>: “<value>” } (writes payload values into Speak custom fields on the uploaded media; values are usually {{trigger.payload.<path>}} tokens. Get field ids from list_fields), folderRouting?: { mode: “static”|“dynamic”, sourceKey (payload key holding the destination folder name, required when dynamic), onNoMatch: “create”|“default” (create a folder named after the value, or fall back to folderId) } } • magic-prompt -> magicPrompt: { prompt (required unless fieldIds given, max 20000), title?, assistantType? (“general”|“researcher”|“marketer”|“sales”|“recruiter”|“custom”, default “general”), assistantTemplateId? (required if assistantType=“custom”), fieldIds?: string[] (max 10. Extract answers into these custom fields), analysisInput? (“transcript” (default) | “audio” | “video”): what the model receives. “audio” lets it hear tone and delivery, “video” also lets it see what is on screen; on a video file “audio” extracts the audio track first. Premium: requires the account’s audio/video analysis opt-in, and costs credits per hour of media } • translation -> translation: { targetLanguage: region-qualified locale code, e.g. “es-ES”, “fr-FR” (bare codes like “es” are rejected) } • filter -> filter: { logic: “AND”|“OR” (default “AND”), rules: [{ field, op, value? }] (1-20) }. The run continues only when the rules match, otherwise it stops silently • condition -> condition: same { logic, rules } shape as filter, but instead of stopping it routes: downstream steps marked branch:“true”/“false” run according to the outcome. Branch wiring rules, all enforced before the request is sent: • A leg is a CHAIN: the first step of a leg depends on the condition, the rest depend on the step before them in the same leg, and every step on the leg carries the same branch marker. • A step that runs after the branch (a merge) depends on the LAST step of every leg that carries on. When a leg is empty its last step IS the condition, and the merge then carries that leg’s marker. • A leg ends the run simply by having nothing depend on its last step. • Once anything carries a branch marker, EVERY other step needs a dependsOn: a step with no parents is an entry point and runs on both branches, and a second entry point is rejected. • A condition with no steps on either side is rejected: both paths would do the same thing. • Branches nest at most three deep, and a nested condition must be the last step of the leg it sits on, or the automation cannot be reopened in the Speak web editor. • A scheduled automation cannot branch: a schedule runs over a batch and a condition resolves once for the whole batch, so the leg would run against media that did not match. Use a filter, which narrows the batch. • notify -> notify: { channel: “in_app”|“email”|“slack”, target?, message (required, tokens allowed) } • outbound-webhook -> outboundWebhook: { url (required, tokens allowed), method? (“GET”|“POST”|“PUT”|“PATCH”|“DELETE”, default “POST”), headers?: { <name>: <value> }, bodyTemplate?: string | object (tokens allowed) } • composio-action -> composio: { app, action, connectedAccountId?, argsTemplate? } (Composio is currently behind a server flag and may be unavailable) Filter/condition rule fields depend on what flows into the step: MEDIA -> name|duration|sourceLanguage|tags|transcript|speakers or a custom field id; INSIGHT (straight after a magic-prompt step) -> answer only, so put a branch on a media field BEFORE the AI step. Neither a filter nor a condition can read the inbound webhook payload (they see the media and earlier step answers only) so a payload path such as “contact.status” is refused: upload first with speakUpload.fieldsMap to write that value into a custom field, then test the field id instead. Ops by field type. Text: eq|neq|contains|ncontains|startsWith|exists; number: eq|neq|gt|lt|exists; array: contains|ncontains|exists (“exists” takes no value; gt/lt values are numbers). Token syntax (usable in fields marked ‘tokens allowed’): {{trigger.payload.<path>}} reads the inbound webhook payload (dot paths and [n] array indices; paths are relative to trigger.childKey when set. Discover valid paths with get_inbound_webhook after sending a test payload); {{step.<stepId>.<path>}} reads a previous step’s output (speak-upload -> mediaId, magic-prompt -> answer, outbound-webhook -> status/response). A positional {{step.<index>.<path>}} form also exists but is REFUSED on a branched automation: the index counts stored order ([condition, true leg, false leg, merge]), not run order, and a step on the branch that was not taken produces nothing. The token would resolve to an empty string inside whatever the step sends. |
trigger |
object | No | Trigger object (the automation’s root). Always include triggerSlug. Supported shapes: • Media analyzed in folder(s): { type: “folders”, triggerSlug: “media_analyzed”, folderIds: string[] (min 1) } • Inbound webhook (receive external payloads): { type: “folders”, triggerSlug: “inbound_webhook”, webhookId? (from provision_inbound_webhook; omit to auto-provision a new one on create), childKey? (dot-path narrowing which part of the payload feeds the automation, e.g. “data”) }. The create/update response includes inboundWebhook.inboundUrl: the public URL to POST payloads to. • Custom field updated: { type: “folders”, triggerSlug: “field_updated”, values: string[] (watched custom field ids, min 1), fieldValueMatches?: [{ fieldId, values: string[] }] (fire only when the field changes TO one of these values; empty values = any change), fieldMatchLogic?: “AND”|“OR” (how multiple fieldValueMatches combine, default “OR”) } • Composio app event: { type: “composio”, provider: “composio”, app, triggerSlug, connectedAccountId } (requires a connected account; may be behind a server flag) Notes: “tags”/“keywords” trigger types are rejected for graph automations. The server stores inbound-webhook triggers with type “webhook” internally: send type “folders” plus the slug as shown above. |
runType |
enum: instant, schedule | No | Run type the graph would be saved with. A schedule refuses any branch. |
Behaviour
The Speak AI MCP server publishes these behaviour hints for
validate_automation_graph.
- Only reads data. Does not modify your workspace.
- Does not delete or overwrite existing data.
- Repeating an identical call is safe.
- Stays inside Speak AI. Does not reach other systems.
Related
- Automations lists the rest of the Automations category.
- Tool reference lists every Speak AI MCP server tool.
- MCP server covers setup, supported clients, and what agents can do once connected.