WhatsApp Flows
Structured, multi-screen forms a WhatsApp conversation can open, built and published from Vitrina.
Download the full API pública projection: openapi.json.
Use WhatsApp templates and Flows explains this resource in prose, with runnable examples.
| Method | Path | What it does |
|---|---|---|
GET | /whatsapp-flows | Live list of a channel's WhatsApp Flows (composer picker) |
GET | /whatsapp-flows/capabilities | What this workspace can build into a WhatsApp Flow |
GET | /whatsapp-flows/managed | List flows built in the Vitrina flow builder |
POST | /whatsapp-flows/managed | Create a flow in the builder |
DELETE | /whatsapp-flows/managed/{id} | Delete a draft flow |
GET | /whatsapp-flows/managed/{id} | Fetch one managed flow |
PATCH | /whatsapp-flows/managed/{id} | Update a flow (drafts and published flows, in place) |
POST | /whatsapp-flows/managed/{id}/convert-to-json | Convert a flow to raw Flow JSON authoring |
POST | /whatsapp-flows/managed/{id}/deprecate | Deprecate a published flow |
POST | /whatsapp-flows/managed/{id}/duplicate | Duplicate a flow as a new draft |
GET | /whatsapp-flows/managed/{id}/flow-json | Get a flow's current Flow JSON |
GET | /whatsapp-flows/managed/{id}/preview | Get (or refresh) a flow preview link |
POST | /whatsapp-flows/managed/{id}/publish | Publish a flow |
PATCH | /whatsapp-flows/managed/{id}/settings | Update a flow's agent settings |
POST | /whatsapp-flows/managed/{id}/test-send | Send a flow to a phone for testing |
POST | /whatsapp-flows/managed/data-preview | Preview the live data of one flow screen |
POST | /whatsapp-flows/sync | Reconcile a channel's managed flows against Meta |
GET /whatsapp-flows
Live list of a channel's WhatsApp Flows (composer picker)
A live proxy read from Meta via the bridge — includes a flow authored outside Vitrina that has not been synced into the builder yet. Each row also carries the agent settings of its local copy (managed_id, key, description, ai_enabled and the default message_*), all null when there is none. messaging_account_id is required; there is no tenant-wide list.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
messaging_account_id | query | uuid | yes |
curl https://api.vitrinadev.com/api/v1/whatsapp-flows \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": [
{
"id": "1200300400500600",
"name": "Agenda tu visita",
"status": "PUBLISHED",
"categories": [
"APPOINTMENT_BOOKING"
],
"managed_id": "e3e3e3e3-0000-4000-8000-000000000001",
"key": "agenda_tu_visita",
"description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
"ai_enabled": true,
"message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
"message_cta": "Agendar visita",
"message_header": null,
"message_footer": null
}
],
"meta": {
"total": 1
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /whatsapp-flows/capabilities
What this workspace can build into a WhatsApp Flow
Discovery for anyone authoring a flow (UI, API, MCP): the Flow JSON and data API versions, the authoring modes, the builder palette (components — every kind with its props, types and bounds, read off the same schema create/update validate with), the live-data data_sources with their params and whether THIS workspace can answer them (booking engine + vertical aware; available: false carries the reason), the context_keys a field can be prefilled from (generic keys + the workspace's custom contact/conversation attributes), the completion_modes and whether booking at submit is possible with this workspace's engine, and the limits. Labels follow the workspace vertical (subject_label: vehicle, property, product, treatment, …). messaging_account_id is required.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
messaging_account_id | query | uuid | yes |
curl https://api.vitrinadev.com/api/v1/whatsapp-flows/capabilities \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
"flow_json_version": "7.3",
"data_api_version": "4.0",
"authoring_modes": [
"builder",
"json"
],
"vertical": "healthcare",
"subject_label": "Tratamiento",
"booking_engine": {
"key": "native-clinic",
"can_book": true,
"booking_link_only": false
},
"components": [
{
"kind": "date_picker",
"props": {
"name": {
"type": "string",
"required": true,
"max": 60,
"min": 1
},
"label": {
"type": "string",
"required": true,
"max": 40,
"min": 1
},
"required": {
"type": "boolean",
"required": false
},
"helper_text": {
"type": "string",
"required": false,
"max": 80
},
"source": {
"type": "object",
"required": false
}
}
}
],
"data_sources": [
{
"source": "available_slots",
"kind": "options",
"label": "Horas disponibles",
"description": "Free hours on one day (`date` required) from the booking engine, after the booking-notice policy.",
"params": [
{
"name": "date",
"required": true
},
{
"name": "service",
"required": false
},
{
"name": "professional",
"required": false
}
],
"available": true,
"reason": null
},
{
"source": "vehicles",
"kind": "options",
"label": "Tratamiento",
"description": "Items of the workspace inventory (today: the vehicle stock); `query` searches, `branch` narrows.",
"params": [
{
"name": "branch",
"required": false
},
{
"name": "query",
"required": false
}
],
"available": false,
"reason": "this workspace's inventory is not exposed as a flow source yet"
}
],
"context_keys": [
{
"key": "subject",
"label": "Tratamiento",
"source": "conversation"
},
{
"key": "contact.name",
"label": "Nombre del contacto",
"source": "contact"
},
{
"key": "contact_attr:prevision",
"label": "Previsión",
"source": "contact_attr"
}
],
"completion_modes": [
{
"mode": "book_then_agent",
"label": "Agendar y que el agente confirme",
"description": "The appointment is booked (or held for a deposit) when the form is submitted; the agent then confirms and continues.",
"books_at_submit": true,
"available": true,
"reason": null
}
],
"limits": {
"max_screens": 10,
"max_components_per_screen": 49,
"max_images_per_screen": 3,
"image_max_bytes": 307200,
"builder_flow_json_max_bytes": 1000000,
"raw_flow_json_max_bytes": 10000000
}
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /whatsapp-flows/managed
List flows built in the Vitrina flow builder
messaging_account_id is required. Health — pushed by Meta’s flows webhook: health_status is blocked / throttled (Meta stopped delivering the flow — sends fail with 132068 / 132069), degraded (an endpoint alert is open: error rate, p90 latency, availability, or the Flow JSON version is about to freeze) or healthy. active_alerts holds the open alerts keyed error_rate:endpoint / error_rate:client / latency / availability / version_expiry (plus status, carrying Meta’s reason, while throttled or blocked); each alert is \{ type, source, state, value, threshold, window, meta_event, message, details, occurred_at \}. last_alert is the most recent health event of any kind, recoveries included; health_updated_at when it was applied.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
messaging_account_id | query | uuid | yes |
curl https://api.vitrinadev.com/api/v1/whatsapp-flows/managed \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": [
{
"id": "e3e3e3e3-0000-4000-8000-000000000001",
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
"meta_flow_id": "1200300400500600",
"name": "Agenda tu visita",
"categories": [
"APPOINTMENT_BOOKING"
],
"status": "PUBLISHED",
"authoring": "builder",
"builder_state": {
"screens": [
{
"title": "¿Cuándo te acomoda?",
"footer_label": "Continuar",
"components": [
{
"kind": "heading",
"text": "Agenda tu visita"
},
{
"kind": "date_picker",
"name": "fecha",
"label": "Fecha preferida",
"required": true
}
]
}
]
},
"flow_json": null,
"validation_errors": null,
"preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
"preview_expires_at": "2026-09-15T19:20:00.000Z",
"key": "agenda_tu_visita",
"description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
"ai_enabled": true,
"message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
"message_cta": "Agendar visita",
"message_header": null,
"message_footer": null,
"completion_mode": "agent",
"field_map": {},
"confirmation_message": null,
"health_status": "degraded",
"active_alerts": {
"latency": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
}
},
"last_alert": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
},
"health_updated_at": "2026-09-15T18:10:02.000Z",
"created_at": "2026-01-10T13:00:00.000Z",
"updated_at": "2026-09-15T18:20:00.000Z"
}
],
"meta": {
"total": 1
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /whatsapp-flows/managed
Create a flow in the builder
builder_state is the ordered screen list; the Flow JSON is generated and uploaded to Meta as part of this call, and validation_errors reports what Meta rejected, if anything. Always created DRAFT.
Authoring — send exactly one of builder_state (the builder: ordered screens of palette components, Flow JSON generated by Vitrina; see GET /whatsapp-flows/capabilities for the palette, data sources and limits of this workspace) or flow_json (advanced: a raw Meta Flow JSON, string or object, uploaded as-is — Meta is the validator and its findings come back as validation_errors; the flow is authoring: "json" from then on). A raw Flow JSON that declares data_api_version gets the workspace data endpoint, but Vitrina's data route only serves builder flows for now — such a flow answers its customers with a clear error instead of data.
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
messaging_account_id | uuid | yes | |
name | string | yes | mín. 1, máx. 200 |
categories | SIGN_UP \ | SIGN_IN \ | APPOINTMENT_BOOKING \ |
builder_state | object | — | |
flow_json | WhatsappFlowJsonInput | — | |
description | string | — | máx. 1000 |
curl -X POST https://api.vitrinadev.com/api/v1/whatsapp-flows/managed \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
"name": "Agenda tu visita",
"categories": [
"APPOINTMENT_BOOKING"
],
"builder_state": {
"screens": [
{
"title": "¿Cuándo te acomoda?",
"footer_label": "Continuar",
"components": [
{
"kind": "heading",
"text": "Agenda tu visita"
},
{
"kind": "date_picker",
"name": "fecha",
"label": "Fecha preferida",
"required": true
}
]
}
]
},
"description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora."
}'Example response (201)
{
"data": {
"id": "e3e3e3e3-0000-4000-8000-000000000001",
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
"meta_flow_id": "1200300400500600",
"name": "Agenda tu visita",
"categories": [
"APPOINTMENT_BOOKING"
],
"status": "DRAFT",
"authoring": "builder",
"builder_state": {
"screens": [
{
"title": "¿Cuándo te acomoda?",
"footer_label": "Continuar",
"components": [
{
"kind": "heading",
"text": "Agenda tu visita"
},
{
"kind": "date_picker",
"name": "fecha",
"label": "Fecha preferida",
"required": true
}
]
}
]
},
"flow_json": null,
"validation_errors": null,
"preview_url": null,
"preview_expires_at": null,
"key": "agenda_tu_visita",
"description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
"ai_enabled": false,
"message_body": null,
"message_cta": null,
"message_header": null,
"message_footer": null,
"completion_mode": "agent",
"field_map": {},
"confirmation_message": null,
"health_status": "degraded",
"active_alerts": {
"latency": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
}
},
"last_alert": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
},
"health_updated_at": "2026-09-15T18:10:02.000Z",
"created_at": "2026-01-10T13:00:00.000Z",
"updated_at": "2026-09-15T18:20:00.000Z"
}
}Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429
DELETE /whatsapp-flows/managed/{id}
Delete a draft flow
Only a DRAFT flow can be deleted (409 otherwise) — deprecate a published one instead.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl -X DELETE https://api.vitrinadev.com/api/v1/whatsapp-flows/managed/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Answers: 204 · 400 · 401 · 403 · 404 · 409 · 429
GET /whatsapp-flows/managed/{id}
Fetch one managed flow
The flow row, including its builder_state (the screen/component tree) and its status on Meta. Health — pushed by Meta’s flows webhook: health_status is blocked / throttled (Meta stopped delivering the flow — sends fail with 132068 / 132069), degraded (an endpoint alert is open: error rate, p90 latency, availability, or the Flow JSON version is about to freeze) or healthy. active_alerts holds the open alerts keyed error_rate:endpoint / error_rate:client / latency / availability / version_expiry (plus status, carrying Meta’s reason, while throttled or blocked); each alert is \{ type, source, state, value, threshold, window, meta_event, message, details, occurred_at \}. last_alert is the most recent health event of any kind, recoveries included; health_updated_at when it was applied.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl https://api.vitrinadev.com/api/v1/whatsapp-flows/managed/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"id": "e3e3e3e3-0000-4000-8000-000000000001",
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
"meta_flow_id": "1200300400500600",
"name": "Agenda tu visita",
"categories": [
"APPOINTMENT_BOOKING"
],
"status": "PUBLISHED",
"authoring": "builder",
"builder_state": {
"screens": [
{
"title": "¿Cuándo te acomoda?",
"footer_label": "Continuar",
"components": [
{
"kind": "heading",
"text": "Agenda tu visita"
},
{
"kind": "date_picker",
"name": "fecha",
"label": "Fecha preferida",
"required": true
}
]
}
]
},
"flow_json": null,
"validation_errors": null,
"preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
"preview_expires_at": "2026-09-15T19:20:00.000Z",
"key": "agenda_tu_visita",
"description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
"ai_enabled": true,
"message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
"message_cta": "Agendar visita",
"message_header": null,
"message_footer": null,
"completion_mode": "agent",
"field_map": {},
"confirmation_message": null,
"health_status": "degraded",
"active_alerts": {
"latency": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
}
},
"last_alert": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
},
"health_updated_at": "2026-09-15T18:10:02.000Z",
"created_at": "2026-01-10T13:00:00.000Z",
"updated_at": "2026-09-15T18:20:00.000Z"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /whatsapp-flows/managed/{id}
Update a flow (drafts and published flows, in place)
Works on a DRAFT and on a PUBLISHED (or throttled / blocked) flow: the Flow JSON is re-uploaded to the SAME Meta flow, which Meta returns to DRAFT until POST /whatsapp-flows/managed/\{id\}/publish publishes it again — status mirrors what Meta reports after the edit. A DEPRECATED flow is final (409).
A builder flow takes builder_state; a raw-JSON flow (authoring: "json") takes flow_json and never a builder_state — convert a builder or external flow first with POST /whatsapp-flows/managed/\{id\}/convert-to-json.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | — | mín. 1, máx. 200 |
categories | SIGN_UP \ | SIGN_IN \ | APPOINTMENT_BOOKING \ |
builder_state | object | — | |
flow_json | WhatsappFlowJsonInput | — |
curl -X PATCH https://api.vitrinadev.com/api/v1/whatsapp-flows/managed/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Agenda tu visita al showroom"
}'Example response (200)
{
"data": {
"id": "e3e3e3e3-0000-4000-8000-000000000001",
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
"meta_flow_id": "1200300400500600",
"name": "Agenda tu visita al showroom",
"categories": [
"APPOINTMENT_BOOKING"
],
"status": "PUBLISHED",
"authoring": "builder",
"builder_state": {
"screens": [
{
"title": "¿Cuándo te acomoda?",
"footer_label": "Continuar",
"components": [
{
"kind": "heading",
"text": "Agenda tu visita"
},
{
"kind": "date_picker",
"name": "fecha",
"label": "Fecha preferida",
"required": true
}
]
}
]
},
"flow_json": null,
"validation_errors": null,
"preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
"preview_expires_at": "2026-09-15T19:20:00.000Z",
"key": "agenda_tu_visita",
"description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
"ai_enabled": true,
"message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
"message_cta": "Agendar visita",
"message_header": null,
"message_footer": null,
"completion_mode": "agent",
"field_map": {},
"confirmation_message": null,
"health_status": "degraded",
"active_alerts": {
"latency": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
}
},
"last_alert": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
},
"health_updated_at": "2026-09-15T18:10:02.000Z",
"created_at": "2026-01-10T13:00:00.000Z",
"updated_at": "2026-09-15T18:20:00.000Z"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /whatsapp-flows/managed/{id}/convert-to-json
Convert a flow to raw Flow JSON authoring
One-way. The flow becomes authoring: "json": a builder flow keeps the Flow JSON the generator produced (its builder_state is dropped — there is no reverse parser), and a flow created outside Vitrina takes its current flow.json downloaded from Meta, which is what makes it editable here (PATCH with flow_json). Nothing is uploaded and the status is untouched; settings, completion and templates keep working. Already json → returned unchanged.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl -X POST https://api.vitrinadev.com/api/v1/whatsapp-flows/managed/<id>/convert-to-json \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"id": "e3e3e3e3-0000-4000-8000-000000000001",
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
"meta_flow_id": "1200300400500600",
"name": "Agenda tu visita",
"categories": [
"APPOINTMENT_BOOKING"
],
"status": "PUBLISHED",
"authoring": "json",
"builder_state": null,
"flow_json": "{\n \"version\": \"7.3\",\n \"screens\": [\n {\n \"id\": \"AGENDA\",\n \"title\": \"Agenda tu visita\",\n \"terminal\": true,\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"form\",\n \"children\": [\n {\n \"type\": \"DatePicker\",\n \"name\": \"fecha\",\n \"label\": \"Fecha preferida\",\n \"required\": true\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Enviar\",\n \"on-click-action\": {\n \"name\": \"complete\",\n \"payload\": {\n \"fecha\": \"${form.fecha}\"\n }\n }\n }\n ]\n }\n ]\n }\n }\n ]\n}",
"validation_errors": null,
"preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
"preview_expires_at": "2026-09-15T19:20:00.000Z",
"key": "agenda_tu_visita",
"description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
"ai_enabled": true,
"message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
"message_cta": "Agendar visita",
"message_header": null,
"message_footer": null,
"completion_mode": "agent",
"field_map": {},
"confirmation_message": null,
"health_status": "degraded",
"active_alerts": {
"latency": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
}
},
"last_alert": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
},
"health_updated_at": "2026-09-15T18:10:02.000Z",
"created_at": "2026-01-10T13:00:00.000Z",
"updated_at": "2026-09-15T18:20:00.000Z"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429 · 502
POST /whatsapp-flows/managed/{id}/deprecate
Deprecate a published flow
Stops it being offered as new; conversations already inside it are unaffected.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl -X POST https://api.vitrinadev.com/api/v1/whatsapp-flows/managed/<id>/deprecate \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"id": "e3e3e3e3-0000-4000-8000-000000000001",
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
"meta_flow_id": "1200300400500600",
"name": "Agenda tu visita",
"categories": [
"APPOINTMENT_BOOKING"
],
"status": "DEPRECATED",
"authoring": "builder",
"builder_state": {
"screens": [
{
"title": "¿Cuándo te acomoda?",
"footer_label": "Continuar",
"components": [
{
"kind": "heading",
"text": "Agenda tu visita"
},
{
"kind": "date_picker",
"name": "fecha",
"label": "Fecha preferida",
"required": true
}
]
}
]
},
"flow_json": null,
"validation_errors": null,
"preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
"preview_expires_at": "2026-09-15T19:20:00.000Z",
"key": "agenda_tu_visita",
"description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
"ai_enabled": true,
"message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
"message_cta": "Agendar visita",
"message_header": null,
"message_footer": null,
"completion_mode": "agent",
"field_map": {},
"confirmation_message": null,
"health_status": "degraded",
"active_alerts": {
"latency": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
}
},
"last_alert": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
},
"health_updated_at": "2026-09-15T18:10:02.000Z",
"created_at": "2026-01-10T13:00:00.000Z",
"updated_at": "2026-09-15T18:20:00.000Z"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /whatsapp-flows/managed/{id}/duplicate
Duplicate a flow as a new draft
Copies the flow's builder_state (or a raw-JSON flow’s flow_json) and its agent and completion settings (except the AI switch) into a brand-new DRAFT flow (a new meta_flow_id), leaving the original untouched. name defaults to the original’s name with a suffix.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | — | mín. 1, máx. 200 |
curl -X POST https://api.vitrinadev.com/api/v1/whatsapp-flows/managed/<id>/duplicate \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Agenda tu visita (copia)"
}'Example response (201)
{
"data": {
"id": "e3e3e3e3-0000-4000-8000-000000000001",
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
"meta_flow_id": "1200300400500600",
"name": "Agenda tu visita (copia)",
"categories": [
"APPOINTMENT_BOOKING"
],
"status": "DRAFT",
"authoring": "builder",
"builder_state": {
"screens": [
{
"title": "¿Cuándo te acomoda?",
"footer_label": "Continuar",
"components": [
{
"kind": "heading",
"text": "Agenda tu visita"
},
{
"kind": "date_picker",
"name": "fecha",
"label": "Fecha preferida",
"required": true
}
]
}
]
},
"flow_json": null,
"validation_errors": null,
"preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
"preview_expires_at": "2026-09-15T19:20:00.000Z",
"key": "agenda_tu_visita_copia",
"description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
"ai_enabled": false,
"message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
"message_cta": "Agendar visita",
"message_header": null,
"message_footer": null,
"completion_mode": "agent",
"field_map": {},
"confirmation_message": null,
"health_status": "degraded",
"active_alerts": {
"latency": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
}
},
"last_alert": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
},
"health_updated_at": "2026-09-15T18:10:02.000Z",
"created_at": "2026-01-10T13:00:00.000Z",
"updated_at": "2026-09-15T18:20:00.000Z"
}
}Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429
GET /whatsapp-flows/managed/{id}/flow-json
Get a flow's current Flow JSON
Whatever its authoring: a raw-JSON flow answers its stored flow_json (source: "stored"), a builder flow the generator output its last save uploaded ("generated"), and a flow created outside Vitrina the flow.json asset downloaded from Meta ("meta", through the bridge). Read-only.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl https://api.vitrinadev.com/api/v1/whatsapp-flows/managed/<id>/flow-json \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"id": "e3e3e3e3-0000-4000-8000-000000000001",
"authoring": "json",
"source": "stored",
"flow_json": "{\n \"version\": \"7.3\",\n \"screens\": [\n {\n \"id\": \"AGENDA\",\n \"title\": \"Agenda tu visita\",\n \"terminal\": true,\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"form\",\n \"children\": [\n {\n \"type\": \"DatePicker\",\n \"name\": \"fecha\",\n \"label\": \"Fecha preferida\",\n \"required\": true\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Enviar\",\n \"on-click-action\": {\n \"name\": \"complete\",\n \"payload\": {\n \"fecha\": \"${form.fecha}\"\n }\n }\n }\n ]\n }\n ]\n }\n }\n ]\n}"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429 · 502
GET /whatsapp-flows/managed/{id}/preview
Get (or refresh) a flow preview link
Read-only and side-effect free by default — answers the cached preview URL. ?refresh=true forces a fresh one through the bridge when the cached one has expired.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes | |
refresh | query | boolean | null | no |
curl https://api.vitrinadev.com/api/v1/whatsapp-flows/managed/<id>/preview \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"id": "e3e3e3e3-0000-4000-8000-000000000001",
"preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
"preview_expires_at": "2026-09-15T19:20:00.000Z",
"status": "PUBLISHED",
"validation_errors": null
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /whatsapp-flows/managed/{id}/publish
Publish a flow
Refused while validation_errors is non-empty. A published flow can still be edited in place (PATCH) and published again.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl -X POST https://api.vitrinadev.com/api/v1/whatsapp-flows/managed/<id>/publish \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"id": "e3e3e3e3-0000-4000-8000-000000000001",
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
"meta_flow_id": "1200300400500600",
"name": "Agenda tu visita",
"categories": [
"APPOINTMENT_BOOKING"
],
"status": "PUBLISHED",
"authoring": "builder",
"builder_state": {
"screens": [
{
"title": "¿Cuándo te acomoda?",
"footer_label": "Continuar",
"components": [
{
"kind": "heading",
"text": "Agenda tu visita"
},
{
"kind": "date_picker",
"name": "fecha",
"label": "Fecha preferida",
"required": true
}
]
}
]
},
"flow_json": null,
"validation_errors": null,
"preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
"preview_expires_at": "2026-09-15T19:20:00.000Z",
"key": "agenda_tu_visita",
"description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
"ai_enabled": true,
"message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
"message_cta": "Agendar visita",
"message_header": null,
"message_footer": null,
"completion_mode": "agent",
"field_map": {},
"confirmation_message": null,
"health_status": "degraded",
"active_alerts": {
"latency": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
}
},
"last_alert": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
},
"health_updated_at": "2026-09-15T18:10:02.000Z",
"created_at": "2026-01-10T13:00:00.000Z",
"updated_at": "2026-09-15T18:20:00.000Z"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /whatsapp-flows/managed/{id}/settings
Update a flow's agent settings
The Vitrina-only settings: key (lowercase slug, unique per WhatsApp number — skills and knowledge documents name the flow by it, in backticks), description (when the AI agent should send it), ai_enabled (the agent may send it while it is PUBLISHED) and the default interactive message (message_body, message_cta, optional message_header / message_footer). Every field is optional; null or a blank string clears one.
None of it is sent to Meta, so this works on a flow in any status — a published flow, and one created outside Vitrina, included.
Completion (ADR 0120): completion_mode — agent (the answers become a customer message and the AI agent answers; the default), book_then_agent (a form with an available-hours field books the chosen hour at submit, then the agent confirms), book_only (books at submit and sends confirmation_message; no AI turn unless the booking failed) or automation_only (no AI turn; only events, webhooks and automations). field_map maps form fields to name, email, rut, phone or attr:<contact attribute key> (replaces the whole map). confirmation_message takes \{\{field\}\} placeholders by field name and \{\{cita\}\} for the booked hour; blank = the default text.
409 flow_key_taken — another flow on the same number uses the key. 422 flow_ai_incomplete — ai_enabled without a description, message body and button label (checked after merging with what the flow already has); flow_completion_mode_unsupported — a booking mode on a form without an available-hours field; flow_field_map_invalid — the map names a field the form lacks.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
key | string | — | patrón ^[a-z0-9][a-z0-9_]{0,63}$ |
description | string | null | — | máx. 1000 |
ai_enabled | boolean | — | |
message_body | string | null | — | máx. 1024 |
message_cta | string | null | — | máx. 30 |
message_header | string | null | — | máx. 60 |
message_footer | string | null | — | máx. 60 |
completion_mode | agent \ | book_then_agent \ | book_only \ |
field_map | object | — | |
confirmation_message | string | null | — | máx. 1024 |
curl -X PATCH https://api.vitrinadev.com/api/v1/whatsapp-flows/managed/<id>/settings \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
"ai_enabled": true,
"message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
"message_cta": "Agendar visita"
}'Example response (200)
{
"data": {
"id": "e3e3e3e3-0000-4000-8000-000000000001",
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
"meta_flow_id": "1200300400500600",
"name": "Agenda tu visita",
"categories": [
"APPOINTMENT_BOOKING"
],
"status": "PUBLISHED",
"authoring": "builder",
"builder_state": {
"screens": [
{
"title": "¿Cuándo te acomoda?",
"footer_label": "Continuar",
"components": [
{
"kind": "heading",
"text": "Agenda tu visita"
},
{
"kind": "date_picker",
"name": "fecha",
"label": "Fecha preferida",
"required": true
}
]
}
]
},
"flow_json": null,
"validation_errors": null,
"preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
"preview_expires_at": "2026-09-15T19:20:00.000Z",
"key": "agenda_tu_visita",
"description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
"ai_enabled": true,
"message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
"message_cta": "Agendar visita",
"message_header": null,
"message_footer": null,
"completion_mode": "agent",
"field_map": {},
"confirmation_message": null,
"health_status": "degraded",
"active_alerts": {
"latency": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
}
},
"last_alert": {
"type": "latency",
"source": null,
"state": "activated",
"value": 8000,
"threshold": 7000,
"window": "30m",
"meta_event": "ENDPOINT_LATENCY",
"message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
"details": {
"p50_latency": 500,
"requests_count": 34
},
"occurred_at": "2026-09-15T18:10:00.000Z"
},
"health_updated_at": "2026-09-15T18:10:02.000Z",
"created_at": "2026-01-10T13:00:00.000Z",
"updated_at": "2026-09-15T18:20:00.000Z"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 422 · 429
POST /whatsapp-flows/managed/{id}/test-send
Send a flow to a phone for testing
Sends the flow — a DRAFT included (mode: draft, its latest draft) — to phone, with a real session behind it: submitting it runs the flow's completion (booking, events, confirmation) like a customer's. When the phone has a live conversation on the flow's WhatsApp number the session is tied to it, so the reply lands there. WhatsApp only delivers inside the phone's open 24-hour window with the number — write to it first; Meta's refusal comes back as a 502 with a Spanish message saying what to do.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
phone | string | yes | mín. 8, máx. 24, patrón ^\+?[0-9 ()-]{8,24}$ |
curl -X POST https://api.vitrinadev.com/api/v1/whatsapp-flows/managed/<id>/test-send \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+56912345678"
}'Example response (200)
{
"data": {
"external_id": "wamid.HBgLNTY5MTIzNDU2NzgVAgARGBI",
"conversation_id": "0190a3b2-5c1d-7e4f-8a9b-0c1d2e3f4a5c",
"mode": "draft",
"session": true
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 422 · 429
POST /whatsapp-flows/managed/data-preview
Preview the live data of one flow screen
For a flow with live data (a Dropdown / Radio / Checkbox with a source, or a DatePicker with available_dates): the screen data the data endpoint would answer for screen_index given answers so far — <field>__options lists, <field>__min / __max / __unavailable dates, carried answers and <field>__value (the selection a refresh keeps). Works on an unsaved builder_state and reads real availability, but never books or writes anything. error_message is the short customer-facing text WhatsApp would show (e.g. no hours left that day).
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
messaging_account_id | uuid | yes | |
builder_state | object | yes | |
screen_index | integer | yes | ≥ 0, ≤ 10 |
answers | object | — | por defecto {} |
context | object | — |
curl -X POST https://api.vitrinadev.com/api/v1/whatsapp-flows/managed/data-preview \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
"screen_index": 1,
"answers": {
"sucursal": "0190a3b2-5c1d-7e4f-8a9b-0c1d2e3f4a5b",
"fecha": "2026-10-06"
},
"builder_state": {
"screens": [
{
"title": "Tu visita",
"components": [
{
"kind": "dropdown",
"name": "sucursal",
"label": "Sucursal",
"required": true,
"options": [],
"source": {
"id": "branches"
}
}
]
},
{
"title": "Fecha y hora",
"components": [
{
"kind": "date_picker",
"name": "fecha",
"label": "Fecha",
"required": true,
"source": {
"id": "available_dates",
"params": {
"branch": {
"field": "sucursal"
}
}
}
},
{
"kind": "dropdown",
"name": "hora",
"label": "Hora",
"required": true,
"options": [],
"source": {
"id": "available_slots",
"params": {
"date": {
"field": "fecha"
},
"branch": {
"field": "sucursal"
}
}
}
}
]
}
]
}
}'Example response (200)
{
"data": {
"data": {
"sucursal": "0190a3b2-5c1d-7e4f-8a9b-0c1d2e3f4a5b",
"fecha__min": "2026-10-05",
"fecha__max": "2026-10-19",
"fecha__unavailable": [
"2026-10-11",
"2026-10-18"
],
"fecha__value": "2026-10-06",
"hora__options": [
{
"id": "2026-10-06T13:30:00.000Z",
"title": "mar 6 oct 10:30"
},
{
"id": "2026-10-06T14:30:00.000Z",
"title": "mar 6 oct 11:30"
}
],
"hora__value": ""
}
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /whatsapp-flows/sync
Reconcile a channel's managed flows against Meta
The most destructive call on this router: local rows Meta no longer reports are deleted. Reports \{ checked, updated, removed \}.
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
messaging_account_id | uuid | yes |
curl -X POST https://api.vitrinadev.com/api/v1/whatsapp-flows/sync \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001"
}'Example response (200)
{
"data": {
"checked": 3,
"updated": 1,
"removed": 0
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429