Skip to content
FousDocs
Open Studio

Create a workflow

Draft a workflow from a sentence, stream it as it is written, write one as JSON, build missing methods, and change it.

From a sentence

Send prompt. Fous reads your methods, picks the ones the request needs, wires their inputs and writes the steps between them.

curl https://api.fous.com/v1/workflows \
  -H "Authorization: Bearer $FOUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Every Monday at 9, list open invoices on our portal and email me a digest",
    "timezone": "Europe/Berlin"
  }'

Returns 200 with the saved workflow, disabled, plus an ai object describing what the AI built (a definition sent instead returns 201). timezone (optional) sets the time zone for times like "9am"; without it, schedules use UTC.

{
  "id": "wf_01m6vnc9ewe3za9g5kqvd8yrkp",
  "name": "Weekly open invoices digest",
  "trigger_type": "schedule",
  "enabled": false,
  "step_count": 3,
  "methods": ["portal.example.com/list_invoices"],
  "incomplete": [],
  "ai": {
    "summary": "Every Monday at 9:00 Berlin time it lists your open invoices on portal.example.com, writes a digest and emails it to you at ada@example.com.",
    "missing": [],
    "reply": null,
    "question": null
  }
}

The response also includes definition. The AI uses only your organization's methods and the inputs their contracts accept. An email step goes to the person making the request unless the prompt names other addresses; with an API key there is no person, so to is left empty.

A value the AI cannot know, such as a webhook URL you did not provide, is left empty and listed in incomplete. When a choice has a sensible default, the AI uses it and says so in summary.

{
  "ai": {
    "summary": "Every Monday at 9:00 it lists your open invoices and posts them to your webhook.",
    "missing": [],
    "reply": null,
    "question": null
  },
  "incomplete": [{ "key": "post", "field": "url", "message": "Add the webhook address" }]
}

Until each value is set with PATCH /v1/workflows/{id}, the workflow cannot run or be enabled: both return 409 conflict with reason: "workflow_incomplete", the incomplete values and any pending steps.

When the AI asks or replies instead

If a step needs a website or account the AI cannot determine ("our supplier portal", "our bank"), the AI asks instead of building. Nothing is saved, and the response is 200 with workflow: null:

{
  "workflow": null,
  "ai": {
    "summary": null,
    "missing": [],
    "reply": "I can build this. Which supplier portal do you use?",
    "question": {
      "text": "Which supplier portal do you use?",
      "options": [{ "label": "Example Portal", "value": "portal.example.com" }],
      "placeholder": "Portal address, e.g. portal.example.com",
      "allow_other": true
    }
  }
}

To answer, send the conversation as messages, newest last, ending with the person's answer:

{
  "messages": [
    { "role": "user", "text": "Each day, summarize new invoices and email finance" },
    { "role": "assistant", "text": "I can build this. Which supplier portal do you use?" },
    { "role": "user", "text": "portal.example.com" }
  ]
}

A prompt that requests no automation ("what can you do?") also saves nothing and returns 200 with workflow: null and the answer in ai.reply. If the AI is unavailable, the response is 503 service_unavailable and nothing is saved.

Watch it being written

With Accept: text/event-stream, the same request returns Server-Sent Events while the AI writes, so you can render the workflow step by step.

curl -N https://api.fous.com/v1/workflows \
  -H "Authorization: Bearer $FOUS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{ "prompt": "Track shipment EX123456789US on carrier.example.com and post its status to https://hooks.example.com/shipments" }'

Events are sent as they happen:

event: status
data: {"text":"Reading your 6 methods"}

event: workflow
data: {"name":"Shipment status to webhook","description":"Tracks a shipment and posts its status."}

event: trigger
data: {"trigger":{"type":"manual","inputs":[]}}

event: step
data: {"index":0,"step":{"key":"track","name":"Track the shipment","type":"method","method":"carrier.example.com/track_shipment","input":{"tracking_number":"EX123456789US"}}}

event: done
data: {"id":"wf_01m6vnd2j8fnh9m45hvyj54nza","name":"Shipment status to webhook","incomplete":[],"ai":{"summary":"When you press Run, it tracks shipment EX123456789US and posts its status to your webhook.","missing":[],"reply":null,"question":null}}
EventData
status{ text }: a short description of what the AI is doing.
question{ text, options, placeholder, allow_other }: the reply asks the person to choose.
reply{ text }: the next piece of a reply, when the AI replies instead of building.
workflow{ name, description }, as soon as they are written.
trigger{ trigger }.
step{ index, step }. Emitted as each top-level step is completed, in order. Loops are emitted with all nested steps included.
reset{}. The initial draft failed validation and the AI is restarting; discard earlier streamed events.
doneThe result, the same body as the JSON response: the workflow with ai, or { workflow: null, ai }.
errorThe error object, as in a JSON response.

A step in a step event may not have its key yet; the definition in done has them all.

From a definition

POST /v1/workflows also accepts { name, description, definition, enabled }, as in the example workflow. A definition is a trigger and a list of steps. A body with prompt or messages is drafted by the AI; any other body is saved as sent.

Fous validates the definition against your methods. An invalid definition returns 400 invalid_request listing all validation errors and their paths:

{
  "error": {
    "code": "invalid_request",
    "message": "2 problems, first steps[0].input.colour: Not an input of portal.example.com/list_invoices. It takes: status, due_before.",
    "errors": [
      {
        "path": "steps[0].input.colour",
        "message": "Not an input of portal.example.com/list_invoices. It takes: status, due_before."
      },
      {
        "path": "steps[1].code",
        "message": "The code reads step \"send\", which has not run yet here: only steps before this one can be read."
      }
    ]
  }
}

A definition is at most 200 KB. A method step must name one of your methods by its slug and use only that method's inputs. Templates and transform code can read only the trigger, earlier steps and, inside a loop, the current item.

Build missing methods

If a request needs a site your organization has no method for, the AI still writes the whole workflow. In place of the missing method it adds a pending step: method is null, and pending gives what the method should do (need), the site to build on, and the request sent to the builder (build_prompt). Later steps read its output using field names inferred from the need, and the step is listed in ai.missing.

{
  "key": "rates",
  "name": "Get today's rates",
  "type": "method",
  "method": null,
  "input": {},
  "pending": {
    "need": "Get the latest exchange rates for the euro against chosen currencies.",
    "site": "rates.example.com",
    "build_prompt": "On rates.example.com, get the latest exchange rates for a base currency against a list of target currencies.",
    "build_id": null,
    "status": "proposed"
  }
}

While a step is pending, the workflow is saved but cannot run or be enabled: both return 409 conflict with reason: "workflow_incomplete" and the pending steps. To build the method and set its site in one request:

curl -X POST https://api.fous.com/v1/workflows/wf_01m6vqpc6yebka97gthqhbype8/steps/rates/build \
  -H "Authorization: Bearer $FOUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "site": "rates.example.com" }'

Returns 202 with build_id. This is an ordinary build, subject to your plan's limits. The body is optional:

FieldMeaning
siteThe website to build on, as a domain or sign-in URL. Replaces the step's site. Saved only when the build starts, so a refused build changes nothing.
connectionsIDs of the connected accounts the build may use.

pending.status changes to building. Poll GET /v1/workflows/{id} every few seconds. When the build ends, the step runs the new method: Fous fills its input from the method's contract and updates later steps to read its real output.

  • A failed build sets pending.status to failed with a reason. Call the endpoint again to retry.
  • A step with no site and no site in the request returns 409 with reason: "site_required".
  • A step that is already building returns its build_id. Sending a different site for it returns 409 conflict.
  • To remove a step, pending or not, call DELETE /v1/workflows/{id}/steps/{key}. What read its output reads nothing: templates are emptied, a filter drops conditions about it, and a transform whose code read it gets new code (a transform with code and no instruction is not rewritten: the removal answers 400 naming it, so change its code first). A value left empty is listed in incomplete.

Change a workflow

PATCH /v1/workflows/{id} updates any of name, description, definition and enabled. A new definition is validated as above and replaces the old one entirely. Enabling a workflow that is not ready returns 409 with reason: "workflow_incomplete". DELETE /v1/workflows/{id} deletes a workflow; its runs stay readable. GET /v1/workflows lists workflows, most recently changed first.

Change it with the AI

Send PATCH /v1/workflows/{id} with prompt, or the conversation so far as messages, newest last, and the AI changes the workflow and saves it:

curl -X PATCH https://api.fous.com/v1/workflows/wf_01m6vnc9ewe3za9g5kqvd8yrkp \
  -H "Authorization: Bearer $FOUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "make it run every day at 8",
    "timezone": "Europe/Berlin"
  }'

The response is the saved workflow with ai, as when creating from a sentence:

{
  "id": "wf_01m6vnc9ewe3za9g5kqvd8yrkp",
  "name": "Weekly open invoices digest",
  "enabled": false,
  "ai": {
    "summary": "It now runs every day at 8:00 Berlin time.",
    "missing": [],
    "reply": null,
    "question": null
  }
}

The AI keeps everything you did not ask to change and reads the last 12 messages. A question, or the AI asking one back, changes nothing: the answer is the workflow as it is, with ai.summary null and its words in ai.reply (or ai.question). With Accept: text/event-stream the response streams as above, and done carries the same body.

A change is saved with the same checks as any PATCH, and a refused change saves nothing: 400 invalid_request, 403 plan_limit, or 409 with reason: "workflow_incomplete" when the workflow is enabled and the change leaves it not ready. If the AI's answer does not hold after a retry, the response is 422 invalid_input with reason: "ai_failed".

Add "preview": true to see the change without saving it: the response is the workflow as it would be, and name and definition in the request are the draft the AI reads. Send it back with PATCH to keep it.

The AI costs no credits within your plan's AI messages.