Skip to main content
Every endpoint below authenticates with a number-scoped API key (x-api-key) whose number is a Meta (Cloud API) number. The WABA is derived from that number — there is no parameter to name one, by design.
flows:read is a slug of its own and is not templates:read. A template listing returns text your workspace wrote; a Flow response returns what a customer typed into a form — a name, a phone, sometimes a document number. The two are gated separately on purpose.

List Flows

id is the local id and is the only id you ever send back to us. metaFlowId is Meta’s own and is read-only — it is null while a Flow exists here but has not reached Meta.

Create or clone

Returns 201 with the new Flow in DRAFT. Omit cloneFromFlowId to start from scratch.
cloneFromFlowId is the local id of the source Flow — the id field, not metaFlowId. Cloning is the only way to change a Flow that has been published.

Rename and re-categorize

Accepts only name and categories. This never publishes and never deprecates — those have endpoints of their own, so that a mistake in a form cannot reach an irreversible operation.

Upload the Flow JSON

A 200 means the document compiled, and validationErrors is then always empty:
A document Meta refuses comes back as 422 FLOW_JSON_INVALID, with Meta’s own list attached:
This is deliberately NOT how Meta’s own API behaves, and the difference is the point.Meta answers 200 with the problems listed inside the payload — so an apparent success over an invalid document is the normal case there, and a client that only checks the status ships a Flow that never compiled.This route reads the list rather than the Graph status, and answers 422 whenever it is non-empty. A 200 from us therefore means what a 200 should mean. Read validationErrors on the 422 — it is Meta’s own answer, verbatim.flowJson is a string, never a nested object: Meta compiles the exact bytes, and re-serializing would silently reorder what you wrote.

Publish

Irreversible on Meta. A published Flow can never be edited or unpublished — only cloned into a new one. "confirm": true is required; without it the request is refused with 400.

Deprecate

Retires the Flow without deleting it, and without deleting the responses people already submitted. supersededByFlowId is optional — but if you send it, it must name a real Flow of yours: an empty string is refused rather than silently ignored, because a broken version chain is invisible afterwards.

Delete

Only a DRAFT can be deleted. Anything else returns 409.

Read form responses

Returns what customers submitted, joined to the contact who answered, within the retention window. Responses past their expiry are never served.

data_exchange endpoint key

Not yet exposed on the public API. The key management for the data_exchange endpoint — generating the keypair, registering the public half with Meta, and reading back whether Meta still holds ours — is built, but has no REST endpoint yet. This section will list it here the day it does.What the feature does, and the design behind it, is described in Flows. If you need it before it is exposed, talk to support.

Errors

Every failure of this surface answers the same envelope — there is exactly one shape to read, and one spelling:
FLOW_JSON_INVALID adds a validationErrors array; FLOW_UNKNOWN_FIELDS adds the offending keys. Never assume the set of codes is closed — branch on the ones you handle and fall through to code for the rest.
The last two carry no FLOW_ prefix — they are shared across the public API and keep their global spelling. A client matching on code.startsWith("FLOW_") would miss both.