Start a flow with the API
Start a published flow for one contact from your own system with POST /flows/{flow_id}/runs, pass its variables, retry safely with an idempotency key, and handle the refusals.
POST /flows/{flow_id}/runs starts one of your flows for one contact, named by phone number. The flow must be published and have a Started by API trigger. To set one up in the builder, see Start a flow from your system.
The key needs the automation.trigger permission. No preset includes it, because a flow can do more than send messages (tag contacts, assign conversations, call your other systems), so a key gets it only by choice: create the key with Custom and tick it. To start a flow for a phone number you don't hold as a contact yet, the key also needs contacts.create. See Create and manage API keys.
Find the flow's ID
Open the flow in Automation and click its Started by API trigger. The panel shows the endpoint with the flow's ID in it, and an example request you can copy.
Start a run
curl -X POST https://iconengage.com/api/public/v1/flows/FLOW_ID/runs \
-H "Authorization: Bearer $ICONENGAGE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042-confirm" \
-d '{
"to": "+91XXXXXXXXXX",
"variables": { "order_number": "1042" }
}'to(required): the customer's phone number in international format, starting with+and the country code, for example"+91XXXXXXXXXX"with the customer's number in place of the X's. A number without the+is refused. If you don't hold the contact yet, it is created.from(optional): the number to send from, by itsidfromGET /numbersor as the number is displayed. Leave it out if your organisation has one number that can send.variables(optional): values for the flow's declared variables, by name, each as text of up to 1,024 characters. A number or yes/no variable takes its text form. A name the flow doesn't declare is refused.Idempotency-Key(required header): one value per run you intend, 8 to 128 characters of letters, digits,.,_,:or-. A retry with the same key and body returns the first run (200) instead of starting another. The same key with a different body is409 duplicate_request.
The answer
201 with the run:
{
"id": "6f1c2e8a-…",
"flow_id": "3b9d41c7-…",
"version": 3,
"contact_id": "a42e9f10-…",
"conversation_id": null,
"phone_number_id": "PHONE_NUMBER_ID",
"state": "running",
"started_at": "2026-10-21T09:30:00Z"
}conversation_id is the customer's open conversation on the sending number, or null until the run's first message opens one. The run follows the flow's published version for its whole life. You can follow it in the flow's Runs tab.
When a run is refused
| Status | code | Why | What to do |
|---|---|---|---|
404 | not_found | No such flow in your organisation. | Check the flow ID. |
409 | conflict, details.reason: flow_not_published | The flow has no published version. | Publish it. |
409 | conflict, details.reason: flow_paused | The flow is paused. | Resume it. See Pause a flow. |
409 | conflict, details.reason: no_api_trigger | The published version has no Started by API trigger. | Add one and publish again. |
409 | conflict, details.reason: run_active | The customer is already in a run of this flow (details.run_id). | Wait for that run to end. |
409 | window_expired | The customer's 24-hour window is closed, and the flow's first message isn't a template (details.node_id). | Start the flow with a Send a template step, or wait for the customer to write. See window_expired. |
409 | template_required | The customer has never written to this number, and the flow's first message isn't a template. | Start the flow with a Send a template step. See template_required. |
409 | contact_opted_out | The customer has opted out. | Don't message them. See contact_opted_out. |
409 | duplicate_request | The Idempotency-Key was used with a different body. | Use a new key for a new run. |
400 | validation_failed | A field is wrong, for example variables.<name> for a value that doesn't fit its variable. | details.fields names each problem. |
403 | permission_denied | The key lacks automation.trigger (or contacts.create for a new number). | Create a key with Custom and tick it. |
429 | quota_exceeded | Too many runs started in a minute for your organisation. | Wait, then retry with the same key. |
For the error format and every other code, see Rate limits and errors.
Still need help?
Write to us at [email protected] and tell us what you were trying to do. Already a customer? Sign in to IconEngage — the same articles are under Help in the app.