Flows
Flows: create one from Flow JSON or an XLSForm, read it with its derived status, the groups that can reach it, its record action and the published Flow JSON, edit its settings, and delete it.
List flows
Every flow your organisation owns, oldest-updated first, paginated with an opaque cursor: follow `next_cursor` until `has_more` is false. Each flow carries its derived `status`, the groups that can reach it, its record action, and a summary of its current and latest versions. Requires the `flows:read` scope.
Create a flow
Creates the flow and its first version, from Flow JSON (`application/json`) or from an uploaded XLSForm or FHIR Questionnaire (`multipart/form-data`, a `source_form` file part beside the metadata fields). Never both. A source form is converted before this responds; its problems are immediate: 422 `conversion_failed` with the converter's sentence, 504 `converter_timeout`, 502 `converter_unavailable`. The response is 202: the flow exists and its first version is `pending` while WhatsApp processes it. Poll `GET /v1/flows/{flow_id}/versions/{version_id}` with `latest_version.id` until `state` leaves `pending`; a rejected document is `failed` with `failure_reason`, an accepted one `draft` with a `preview_url`, and `publishable` says when it can be published. `warnings` lists what the converter dropped or degraded; it is empty for Flow JSON. `number_id` defaults to your organisation's number (422 `no_number_configured` when it has none) and must be one of yours. `record_action` is not accepted here; set it through PUT /v1/flows/{flow_id}/record-action once the version has left `pending`, then publish. Bodies up to 5 MB. `Idempotency-Key` is accepted but not required: send one and a retry of the same request replays the first response as 200 with `Idempotent-Replayed: true` instead of creating a second flow. Requires the `flows:write` scope.
Delete a flow
Deletes the flow, every version, **every submission collected through it and their stored files**, in the background: 202 now, and the flow answers 404 once the job has run, usually within seconds; until then it reads unchanged. There is no undo. A published flow must be unpublished first (409 `must_unpublish_first`). Requires the `flows:write` scope, which is therefore a destructive scope: keep keys that hold it as carefully as the data.
Get a flow
One flow, exactly as it appears in the list. 404 for a flow this key cannot see — one that does not exist, or one another organisation owns. Both answer identically: no existence oracle. Requires the `flows:read` scope.
Update a flow
Changes the flow's settings: name, description, confirmation message, visibility and notify contacts. Omitted keys keep their value. Ownership, status and versions are never writable here, and the record action has its own endpoint. Making a private flow public claims its keywords across Hop; 409 `code_conflict` when one is already taken. It also opens the flow to anyone: a public flow accepts a submission from any phone number that texts its keyword and creates the contact on first use, where a private flow accepts only contacts your organisation already has. Requires the `flows:write` scope.
Remove the flow's record action
Future completions stop writing records. Existing records and submissions are never touched by this call. The flow's own backing register goes with the action only when the action provisioned it, it holds no record, and nothing else uses it; a register with records, one that belongs to another flow, or one another flow's action writes into stays. Idempotent: an absent action is 204 too. 409 `register_in_use` when an option list of some flow still reads the register that would go; the detail says which flow, or that it is this flow's own version. Requires `flows:write`.
Create or replace the flow's record action
The API face of the editor's Records card: what completing this flow does to a register. A full replacement; 201 when the flow had no action, 200 when it had one. Omitting `register_id` uses the flow's own backing register, created and named after the flow the first time and reused after that. `record_id_key` must be an option list of the flow's current version bound to that register; `label_key` and every `excluded_keys` entry must be fields of that version (any named component, including pickers). `update` or `create_or_update` with a null `record_id_key` is allowed, and means only sends started from a record can aim the flow. Field-referencing settings need a version WhatsApp has processed: while the flow's version is still `pending` they are 422 `validation_failed` (`no_processed_version`); a field-free action (`mode` alone) is accepted at any time. Other `errors[]` codes: `unknown`, `not_register_bound`, `not_applicable` (a `record_id_key` with `create`), `not_a_flow_register` (a CSV register). The current version is the published one when there is one, else the newest. No `Idempotency-Key`: the request is a full replace, so retrying after a lost response lands on the same state; the only difference a retry sees is 200 where the first attempt would have said 201. Requires `flows:write`.
Get the published Flow JSON
The WhatsApp Flow JSON document of the flow's current published version, exactly as stored, with no envelope. Pass `version` to read a specific version's document in any state — a draft still uploading, a failed one, a deprecated predecessor. Without `version`, a flow that has no published version answers 422 `no_published_version`, the same code `POST /v1/flows/{flow_id}/submissions` sends for the same condition. Requires the `flows:read` scope.
Download the original XLSForm
Redirects (302, empty body) to a time-limited signed URL for the XLSForm the flow's current version was uploaded from, or its latest version when none is published, saved under its original file name. 404 for a flow this key cannot see, and 404 for a flow that was authored as Flow JSON or whose source is not stored as a file; the `detail` says which. Requires the `flows:read` scope.