Documentation / Workflows

The workflow definition (for AI assistants)

Minimum role
Organisation admin
Applies per
organisation
Last updated
11 October 2026
aishaa version
80c056408

Menu path: No screen of its own; through the aishaa connection in Claude or ChatGPT, publishing in Account > Workflows

In short: a workflow is data: one JSON object with a trigger, an optional concurrency key and a list of steps. An AI assistant (aishaa’s onboarding, or a customer’s Claude or ChatGPT through the aishaa connection) can write such a definition and save it as a draft with the workflows_save_draft tool. A draft does nothing: it only works once a person publishes it in aishaa. This article gives the complete format, all checks with their error messages, the tools and a step-by-step plan.

Where to find this

There is no separate screen for the definition. An AI assistant works with it through aishaa’s MCP connection; how to set that up is described in Connect aishaa to Claude or ChatGPT. A person checks and publishes the draft under Account > Workflows with Publish.

Who: the workflow tools are available from the Organisation admin role, for the organisations whose AI settings you manage. Only an Account admin can create or change a workflow for the whole account. The assistant always works with exactly your permissions.

The format

{
  "trigger": {
    "event": "<event>",
    "conditions": [ { "path": "trigger.<field>", "op": "<operator>", "value": <value> } ],
    "schedule": { "every": "day", "at": "09:00" }
  },
  "concurrencyKey": "trigger.ticketId",
  "steps": [
    {
      "id": "<step name>",
      "label": "<what the step does, in the customer's language>",
      "block": "<block name>",
      "inputs": { "<input>": <value or "{{reference}}"> },
      "next": "<step name>",
      "exits": { "<exit>": "<step name>" },
      "onError": "<step name>"
    }
  ]
}

The names are camelCase, exactly as below.

Part Required Meaning
trigger.event yes The technical name of the event, such as message.understood. See Triggers and events.
trigger.conditions no List of conditions that must all be met. Left out: every event of that kind.
conditions[].path yes One of the event’s own fields, without curly brackets, starting with trigger..
conditions[].op yes eq, neq, gt, gte, lt, lte, contains, exists, missing.
conditions[].value except with exists/missing The fixed value, with the correct JSON type (a number as a number).
trigger.schedule only with schedule.due, and then required every (hour, day, weekday, week, month), at (HH:mm), days (with week: monday … sunday), dayOfMonth (with month: 1 to 28), timeZone (optional, such as Europe/Amsterdam).
concurrencyKey no One of the event’s own fields (such as trigger.ticketId) for which the workflow never has two open runs at the same time.
steps yes 1 to 100 steps. The first step is where a run starts.
steps[].id yes Unique; lowercase letters, digits, - and _, starting with a letter or digit; at most 64 characters; never trigger.
steps[].label no, but always fill it in What the step does for this organisation, in a few words in its language, at most 120 characters.
steps[].block yes The technical name of the block, such as agent.reply. See Building blocks of a workflow.
steps[].inputs depends on the block The input of the block: fixed values or references {{trigger.<field>}} / {{<stepId>.<field>}}.
steps[].next only for a block without exits The next step. Left out: end of the run.
steps[].exits only for a block with exits The next step per exit. An exit left out: end of the run.
steps[].onError no The step the run goes to if this step fails. The reason is then in {{<stepId>.error}}.

All rules for references, routing, repeating and errors are in References, routing, repeating and choosing.

Complete example

A workflow that sorts every customer message, puts on a label and replies per route, with a question to a colleague for urgent complaints:

{
  "trigger": { "event": "message.understood" },
  "steps": [
    { "id": "sorteer", "label": "Decide what the message is about", "block": "agent.choose",
      "inputs": {
        "options": ["retour", "levering", "klacht"],
        "explanations": {
          "retour": "wants to send something back or exchange it",
          "levering": "asks where an order is or when it will arrive",
          "klacht": "is unhappy with a product or the service"
        },
        "labels": { "retour": 12, "levering": 13, "klacht": 14 },
        "input": "{{trigger.message.text}}"
      },
      "exits": { "retour": "antwoord-retour", "levering": "antwoord", "klacht": "dringend", "other": "antwoord" } },
    { "id": "antwoord-retour", "label": "Answer the return question", "block": "agent.reply",
      "inputs": { "instruction": "Always refer to the return form on the website." },
      "exits": { "failed": "taak" } },
    { "id": "dringend", "label": "Is the complaint urgent?", "block": "flow.condition",
      "inputs": { "conditions": [ { "path": "trigger.urgent", "op": "eq", "value": true } ] },
      "exits": { "true": "teamleider", "false": "antwoord" } },
    { "id": "teamleider", "label": "Put the urgent complaint before the team lead", "block": "ticket.update",
      "inputs": { "team": 4, "urgent": "yes" }, "next": "antwoord" },
    { "id": "antwoord", "label": "Answer the message", "block": "agent.reply", "inputs": {},
      "exits": { "failed": "taak" } },
    { "id": "taak", "label": "Task: write the reply manually", "block": "task.create",
      "inputs": { "title": "Answer the message yourself", "text": "aishaa could not write a proposal for this message." } }
  ]
}

The ids of the labels (12, 13, 14) and the team (4) are examples: the real ones are in the guide from workflows_get_guide under “Labels” and “Teams”.

Checks and error messages

When saving through workflows_save_draft and when reading with workflows_get, aishaa returns all problems at once, as an English message with which an AI can improve the draft. In the editor, a person sees the same problems under Before you can publish. A draft with problems is saved, but cannot be published.

Definition and trigger

Code Message (as the tool gives it) In the editor
definition_missing The workflow has no definition. The workflow has no trigger or steps yet.
definition_invalid The definition is not valid JSON for a workflow: … The draft could not be read.
trigger_unknown The trigger names no known event type (‘…’). Choose what starts the workflow.
path_not_trigger A trigger condition must read from the trigger (‘trigger.…’), not ‘…’. (Or: The concurrency key must read …) A condition of the trigger must use a field of the trigger, not ‘…’.
trigger_reads_content A trigger condition reads ‘…’, the content of an object; trigger conditions read the event’s own fields only. Test it in a flow.condition step. (Or: The concurrency key reads …) A trigger condition reads ‘…’, content that is read only by a step. Test it in a step “Condition” or “Choose a route”.
schedule_without_trigger Only the event ‘schedule.due’ takes a schedule; remove trigger.schedule. Only “On a schedule” takes a schedule.
schedule_missing A workflow on the clock needs trigger.schedule: {“every”: “day”, “at”: “09:00”}. Say when the workflow starts.
schedule_invalid trigger.schedule: ‘every’ is one of: hour, day, weekday, week, month. / ‘at’ is a time as HH:mm, such as 09:00. / Every week needs ‘days’: one or more of monday, tuesday, wednesday, thursday, friday, saturday, sunday. / Every month needs ‘dayOfMonth’ from 1 to 28. / ‘…’ is not a time zone (such as Europe/Amsterdam). The schedule is not complete: …

You get a definition_invalid for JSON that does not fit, for example an unknown operator or a missing trigger or steps. workflows_save_draft does not save such a definition and answers with “definitionJson is not a workflow definition: … Read workflows_get_guide.”

Steps and routing

Code Message In the editor
step_empty A step in the list is empty (null). There is an empty step.
no_steps A workflow needs at least one step. Add at least one step.
too_many_steps A workflow has at most 100 steps. A workflow has at most 100 steps.
step_id_invalid Step id ‘…’ must be lowercase letters, digits, ‘-‘ or ‘_’. (also for more than 64 characters) Step ‘…’: a step name is lowercase letters, digits, ‘-‘ or ‘_’.
step_id_reserved A step cannot be called ‘trigger’. A step cannot be called ‘trigger’.
step_id_duplicate Step id ‘…’ is used twice. Two steps are called ‘…’.
step_label_too_long Step ‘…’: the label is longer than 120 characters. Step …: keep what it does to at most 120 characters.
block_unknown Step ‘…’: block ‘…’ does not exist. Step ‘…’: this kind of step does not exist (…).
exits_without_exits Step ‘…’: block ‘…’ has no exits; use ‘next’. Step ‘…’: this step goes on with ‘Then’, not per outcome.
next_with_exits Step ‘…’: block ‘…’ leaves by its exits (…), not by ‘next’. Step ‘…’: this step goes on per outcome, not with ‘Then’.
exit_unknown Step ‘…’: block ‘…’ has no exit ‘…’. Step ‘…’: there is no outcome ‘…’.
target_missing Step ‘…’ leads to step ‘…’, which does not exist. Step ‘…’ leads to ‘…’, which does not exist.
error_route_to_itself Step ‘…’: ‘onError’ cannot lead to the step itself. Step ‘…’: if it fails, it cannot go to itself.
reference_unknown Step ‘…’ refers to ‘…’: ‘…’ is neither the trigger nor a step. Step ‘…’ uses ‘…’, but that step does not exist.
answer_without_understood Step ‘…’: ‘agent.reply’ (or ‘case.process’) works on a customer message the analysis understood; the trigger must be ‘message.understood’. Step “…” answers a customer message: the workflow must start on “Message read”.
loop_without_steps Step ‘…’: the repeat needs its ‘each’ exit to lead to the steps for every item. Step ‘…’: “for each item” must lead to the steps for one item.
loop_leaks Step ‘…’: step ‘…’ runs for every item but is also reached outside the repeat. The steps for each item end; the repeat then goes on by ‘done’. Step ‘…’: step ‘…’ runs for each item but is also reached outside the repeat.

If a block is unknown, aishaa does not check the rest of that step. aishaa only checks loop_leaks when there are no other problems.

Input per block

Problems with the input of a block have the code input_required (a required input is missing) or input_invalid (an input is not right). Every message starts with Step '<id>':. The editor shows “Step ‘…’: fill in ‘…’.” or “Step ‘…’: ‘…’ is not right yet.”

Block Message (after Step '<id>':)
flow.condition input ‘conditions’ must be a list of conditions with a path and an operator.
flow.wait give either ‘minutes’ or ‘until’. / ‘until’ must be a moment (ISO 8601) or a reference in {{…}}. / ‘minutes’ must be a number above 0 and at most 30 days.
flow.switch ‘value’ (what to choose by) is required. / ‘cases’ must list 1 to 12 different values (not ‘other’), each at most 80 characters.
flow.foreach ‘items’ must be a reference to a list, such as {{find.records}}. / the repeat needs its ‘each’ exit to lead to the steps for every item.
message.check ‘check’ is hasImage, noImage, recordFound or noRecord.
human.request ‘title’ (the question) is required. / ‘exits’ must list 1 to 6 different answers in lowercase (letters, digits, ‘-‘, ‘_’), not ‘expired’. / ‘exitLabels’ gives each answer the words on its button (at most 60 characters), only for answers in ‘exits’. / ‘fields’ must be a JSON schema (an object). / ‘deadlineMinutes’ must be a number above 0 and at most 30 days.
task.create ‘title’ is required. / ‘dueMinutes’ must be a number above 0 and at most 365 days.
plugin.action ‘action’ must name a plugin action as ‘pluginId/operationId’. / ‘input’ must be an object of the action’s parameters. / ‘mode’ is ‘propose’ or ‘execute’. / ‘deadlineMinutes’ must be a number above 0 and at most 30 days.
message.send ‘message’ (the text to the customer) is required. / ‘mode’ is ‘propose’ or ‘execute’. / ‘deadlineMinutes’ must be a number above 0 and at most 30 days.
ticket.forward ‘to’ (an e-mail address) is required. / ‘mode’ is ‘propose’ or ‘execute’. / ‘deadlineMinutes’ must be a number above 0 and at most 30 days.
ticket.label ‘label’ (a label’s id) is required.
ticket.update ‘status’ is one of keep, open, answered, closed. / ‘important’ is one of keep, yes, no. / ‘urgent’ is one of keep, yes, no. / ‘team’ is a team’s id.
ai.transform ‘instruction’ (what the AI should do) is required. / ‘output’ must be a JSON schema of an object.
data.format ‘operation’ is one of: add_time, format_date, calculate, round, replace, upper, lower, truncate, default, join, count. / ‘value’ (what to work on) is required. / ‘unit’ is one of: minutes, hours, days, weeks, months. / ‘…’ needs ‘amount’ (a number or a reference). / ‘arithmetic’ is one of: add, subtract, multiply, divide. / ‘format’ is one of: date, date_time, time, weekday, iso, iso_date. / ‘replace’ needs ‘find’.
record.find ‘kind’ (which kind of record) is required.
record.read ‘read’ must name a record read as ‘pluginId/operationId’. / ‘record’ (which record, usually {{find.recordId}}) is required.
plugin.read ‘read’ must name a plugin read as ‘pluginId/operationId’. / ‘input’ must be an object of the read’s parameters.
document.create ‘title’ is required. / ‘text’ is required.
agent.reply ‘agent’ is an agent’s id, or left out for the conversation’s agent. / ‘instruction’ is text of at most 2000 characters. / ‘send’ is ‘where_granted’ or ‘propose’.
agent.choose ‘agent’ is an agent’s id, or left out. / ‘options’ must list 1 to 30 different options (not ‘other’), each at most 80 characters. / ‘explanations’ gives options in ‘options’ a text of at most 400 characters. / ‘labels’ gives options in ‘options’ the id of a label. / ‘input’ (what the agent reads) is required.
agent.task ‘agent’ is an agent’s id, or left out for the agent the answering workflow names. / ‘instruction’ (the task) is required. / ‘instruction’ is at most 4000 characters.
case.process no input, no checks.

What the check does not check

  • Whether a plugin action, plugin read or record read really exists in the organisation (only the form pluginId/operationId). That becomes clear when the workflow runs.
  • Whether a label, team, agent or colleague with that id exists. A wrong id makes the step fail when it runs.
  • Whether a referenced field exists, and whether the step you refer to has already run.

That is why you only use ids and names from the guide from workflows_get_guide.

The MCP tools for workflows

Tool What it does
workflows_get_guide Gives the guide for writing a workflow. Always read it first.
workflows_list Shows the account’s workflows that this organisation sees.
workflows_get Reads one workflow: the draft, the versions, what blocks publishing, and optionally the latest runs.
workflows_save_draft Creates a workflow or replaces the draft of an existing one.

Every tool has the optional parameter organisationId. Leave it out if you have access to one organisation; pass it if you have access to several.

workflows_get_guide

The guide is rebuilt every time from what is in aishaa at that moment. It always gives the current catalogue plus what the organisation has:

  • the rules of the format (trigger, conditions, steps, routing, references, fixed values, show and fields, content fields, error routes, repeating, choosing, calculating, connected systems, the schedule, answering customer messages);
  • every event with all its fields, their type, choice values and whether it is content;
  • every block you can add, with kind, description, input (name, type, required, choice values, explanation), exits and output fields;
  • under “This organisation”: the plugin actions with their parameter schema (at most 40 actions, a long schema shortened), the record kinds, the plugin reads with their schema (at most 40), the record reads with the record kinds they read, the agents (id, name and what for), the labels (id and name), the teams (id and name) and the colleagues who can be given work (user id and name).

If a part says there is nothing (“none”, “cannot be used here”), the organisation cannot use that block or you must leave out that input.

workflows_list

Per workflow: workflowId, name, description, status (Draft, Active or Paused), scope (account or an organisation), the event of the trigger, the live version, whether the draft has unpublished changes, the number of runs and failed runs in the past week, the number of open runs, the last run, and appUrl: the link to the workflow in aishaa.

workflows_get

Parameters: workflowId (required) and includeRuns (default false). Gives the name, description, status, scope, whether the workflow is locked, the draft (draft, the definition you replace with workflows_save_draft), the live version, the earlier versions (number, date, by whom), the problems that block publishing, and appUrl. With includeRuns: true also the latest 20 runs (status, version, where the run is, start, end, until when it waits, error, ticket) and, for the latest events of the trigger, whether they started a run or which condition stopped that, with the actual value.

workflows_save_draft

Parameters:

Parameter Required Explanation
definitionJson yes The whole definition as JSON text. Always pass the complete definition, also for a small change.
workflowId no The workflow you are changing. Left out: a new workflow is created.
name for a new workflow The name people see, at most 200 characters.
description no What the workflow is for, in one or two sentences (at most 2,000 characters).
wholeAccount no true: a new workflow for all organisations of the account (only for an Account admin). Default: for the organisation you are working in.

The answer contains workflowId, saved, readyToPublish (true if there are no problems), problems (the messages from the tables above), appUrl and next: “Ask the person to check the draft and publish it in the app (appUrl).” or “Fix the problems and save again.”

Good to know:

  • A new workflow starts as a draft (status Draft). Saving changes nothing about what is live.
  • A workflow that is locked does not accept a new draft.
  • There is deliberately no tool to publish. A person checks the draft and publishes it in aishaa, through appUrl.

A draft only works after publishing

A draft does nothing with customers or data. Only when a person clicks Publish in Account > Workflows does it become a live version (status Active) and do runs start. In the editor that person can first try it out on real events without anything happening; see Create, test and publish a workflow. A run always continues on the version on which it started.

The editor itself also has an assistant, the Building assistant, which proposes a new draft based on what someone asks. It uses the same guide as workflows_get_guide.

How to design a workflow for a customer as an AI

  1. Read the guide. Call workflows_get_guide for the right organisation. Only use events, blocks, fields, actions, reads, record kinds, agents, labels, teams and colleagues that are in it. Never make up an id.
  2. See what is already there. Call workflows_list. If a workflow on message.understood already answers customer messages, change that one (read it with workflows_get) instead of putting a second one next to it. Two workflows that answer the same message each write their own proposal.
  3. Ask what happens and when. Let the customer tell you in their own words which situation they want to automate: what comes in, what should happen then, who decides on it, and what may happen without a person looking along.
  4. Choose the trigger. Customer messages: message.understood. A new conversation: ticket.created. A recording: recording.transcribed. A closed conversation or an assignment: ticket.status_changed or ticket.assigned. Something at fixed times: schedule.due with a schedule. Narrow it down with conditions on the event’s own fields (channel, topic, opening hours, new conversation). Only test content in a step.
  5. Sort. If messages need to be handled differently, start with a choice: flow.switch on a fixed value (channel, trigger.topicId) if possible, otherwise agent.choose with short options, an explanation per option and possibly a label. Always connect other.
  6. Choose per route: reply or person. A route that gets a reply ends in agent.reply, possibly with an instruction for that route. A route where a person has to decide gets a human.request or a ticket.update to the right colleague or the right team.
  7. Fetch data. Use content fields where possible (no step needed), record.find and record.read for the customer’s record, plugin.read (possibly in a chain) for other data, and data.format for dates, amounts and lists. Never let the AI calculate.
  8. Ask for approval of actions. Keep plugin.action, message.send and ticket.forward on "mode": "propose" until the customer trusts the workflow. Give a summary, an assignee where a specific colleague must decide and a suitable deadlineMinutes. Connect rejected and expired if something still needs to happen then.
  9. Catch errors. Connect the exits none and failed, and give steps where something can go wrong an onError to a task.create with {{<step>.error}} in the text. A message that no workflow answers is for a colleague.
  10. Write readably. Give every step a short, clear id and a label in the customer’s language. Write titles, questions, button texts (exitLabels) and summaries in that language. Only put in show what helps to decide, never ids.
  11. Save the draft. Call workflows_save_draft with the complete definition, a name and a description. If there are problems, fix them and save again until readyToPublish is true.
  12. Let a person publish. Tell the customer in plain language what the workflow does, what they should check (who gets a question, which deadlines, what happens at once) and give the appUrl. Recommend trying it out first. Never publish yourself; there is no tool for that either.

Good to know

  • A workflow has no priority: every workflow whose trigger matches starts. That is why you put all of the answering of a message in one workflow.
  • A workflow runs in the background on behalf of the workflow. What a person must approve is a human.request, a plugin.action on propose or a message.send on propose.
  • An agent.reply only sends by itself where a person has allowed the agent to send by itself on that topic and every guardrail holds; otherwise it remains a proposal.
  • Through the connection, an AI assistant never sees or does more than the person who is logged in.

Related

Last updated on 11 October 2026. Checked against aishaa version 80c056408. Something wrong or missing? Email support@aishaa.nl.