---
title: "Create an approval flow from a description"
method: POST
path: "/api/v1/rest/workflow/flows"
tags: ["Workflows"]
---

# Create an approval flow from a description

`POST /api/v1/rest/workflow/flows`

## Request

- Base URL: `https://api.custodo.ai`
- URL: `https://api.custodo.ai/api/v1/rest/workflow/flows`
- Auth: HTTP bearer

Saves a new flow (a reusable approval process) from what the person described: the form fields, then the approval steps in order — who is asked at each (a role, a member from custodo_list_members or a reparto from custodo_list_teams), whether all or any of them must approve, the deadline, and when a step is skipped (condition). The person approves the exact flow before it is saved, and it is saved SWITCHED OFF: say so, and give them `open_in_builder`. Prefer roles or named members over guessing; ask when the description does not say who approves.

## Headers

- `X-Tenant-Id` string, uuid

## Request body

- object
  - `name` string, required
  - `description` string
  - `trigger` 'manual' | 'document_ready' | 'document_typed' | 'email_received' | 'crm_deal_won' | 'spese_report_submitted': manual = a person starts it; document_ready / document_typed / email_received / crm_deal_won / spese_report_submitted start it automatically.
  - `document_type_id` string, uuid: document_* triggers only: the class that starts it (documents_list_types).
  - `trigger_config` object: The automatic trigger’s filter: document_* {titleContains}; email_received {mailboxIds, subjectContains, fromContains, toContains, requireAttachments, pec: any|only|exclude}; crm_deal_won {pipelineIds, minAmount}; spese_report_submitted {minTotalCents}. Omit for none — but ALWAYS narrow email_received, or every synced message opens a run.
  - `priority` 'low' | 'normal' | 'high'
  - `fields` object[]: What the person starting the flow fills in.
    - `key` string: Stable key conditions refer to. Omit to derive it from the label.
    - `label` string, required
    - `type` 'text' | 'textarea' | 'number' | 'date' | 'boolean' | 'select', required
    - `required` boolean
    - `options` string[]
    - `help` string
  - `steps` object[], required: The approval steps, in order. Automated steps are added by a person in the builder.
    - `id` string: workflow_update_flow only: the id of the existing step this replaces.
    - `name` string, required
    - `assignees` object[], required
      - `role` 'admin' | 'user': `admin` = every administrator; `user` = every member with approval rights.
      - `user_id` string, uuid: One member — from custodo_list_members.
      - `team_id` string, uuid: A reparto — from custodo_list_teams.
    - `mode` 'all' | 'any' | 'quorum': With several assignees: all must approve, any one may, or a quorum.
    - `quorum` integer
    - `timeout_hours` integer
    - `parallel_with_previous` boolean: Asked at the same time as the step above instead of after it.
    - `condition` object: Omit to run the step always.
      - `match` 'all' | 'any'
      - `rules` object[], required
        - `field` string, required: A field key (or label) of this flow, or a run fact such as @totalCents.
        - `op` 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'contains' | 'truthy' | 'falsy', required
        - `value` union: Omit for truthy/falsy. For a select, one of its options exactly as written.
          - string
          - number
          - boolean
          - union[]
            - union
              - …

## Response `200`

Done. `data` is the tool’s own answer.

- RestResult
  - `ok` true, required
  - `data` unknown, required

## Other responses

- `400`: The arguments did not validate: `error.message` names the field, what it expects and what arrived.
- `401`: No API key, or one that is malformed, unknown, revoked or expired.
- `403`: The call never ran: the module is off for this organisation, the credential lacks the permission or the module, the tier does not cover it, or the tool is destructive and the key was not minted for that.
- `409`: The record is in a state that does not allow it — under legal hold, already sent, already closed. `error.code` says which.
- `422`: The tool ran and refused, in a sentence of its own (`TOOL_REFUSED`): the message says what to change. Retrying the same call will not help.
- `429`: The API key has spent its minute, or its day. Every request a key makes counts. See `Retry-After`.

---

[API](https://skmtc.dev/custodo/apis/custodo-integrazioni.md) · [All operations](https://skmtc.dev/custodo/apis/custodo-integrazioni/llms.txt) · [OpenAPI document](https://skmtc.dev/custodo/apis/custodo-integrazioni/revisions/16d336d3ec2a?raw)
