Docs / Strand / getting-started/trigger-over-http
Trigger a workflow over HTTP
Strand has no client SDK, and for triggering it doesn't need one: starting a workflow from your own code is a single authenticated POST. This page shows that call, with copy-paste snippets in curl, Python, JavaScript, and Go — and where to reach for event-driven triggering instead.
Before you start#
- An API key. Create one in the Strand dashboard under Access Control → API Keys.
- A saved workflow. Its ID is in the editor URL and the workflow list. A workflow runs
- The base URL is
https://app.tendrl.com/strand.
Send it on every request as Authorization: Bearer <API_KEY>.
the latest saved version — there is no separate "published" version to manage.
The call is asynchronous: a 200 means the run was queued, not that it finished. You get a run_id back to check the result (see Check the result), and the workflow itself delivers its output through its connector nodes.
Run a workflow#
POST https://app.tendrl.com/strand/api/workflows/{workflow_id}/run
Use this whenever your code knows which workflow to start. No setup beyond the API key. The body is {"data": { ... }}, where data is the event your workflow receives — shape it like the real thing, because your nodes read from it via templating.
curl -X POST https://app.tendrl.com/strand/api/workflows/YOUR_WORKFLOW_ID/run \
-H "Authorization: Bearer $STRAND_API_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {"temperature": 22.5, "device": "sensor-1"}}'
Response:
{
"message": "Workflow execution queued",
"run_id": "542cab88_4ab4_49bc_ae16_bc0b05731fc2",
"workflow_version_id": "1f9f7586_c749_436e_b4e2_6b12f7204ab1",
"version": 1
}
The same call in other languages:
resp = requests.post(
"https://app.tendrl.com/strand/api/workflows/YOUR_WORKFLOW_ID/run",
headers={"Authorization": f"Bearer {os.environ['STRAND_API_KEY']}"},
json={"data": {"temperature": 22.5, "device": "sensor-1"}},
)
resp.raise_for_status()
run_id = resp.json()["run_id"]
const resp = await fetch(
"https://app.tendrl.com/strand/api/workflows/YOUR_WORKFLOW_ID/run",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.STRAND_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ data: { temperature: 22.5, device: "sensor-1" } }),
},
);
if (!resp.ok) throw new Error(`Strand trigger failed: ${resp.status}`);
const { run_id } = await resp.json();
body := strings.NewReader(`{"data": {"temperature": 22.5, "device": "sensor-1"}}`)
req, _ := http.NewRequest("POST",
"https://app.tendrl.com/strand/api/workflows/YOUR_WORKFLOW_ID/run", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("STRAND_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
Rate limit: 60 requests per minute per account.
Event-driven triggering (no HTTP call)#
If the goal is "a device reading or a message should start a workflow," you usually don't make this call at all — you let the event route itself by tags. Give a workflow tags and turn on Expose to Contact, and any Contact message whose tags match starts it automatically. One message can start several workflows this way, with no code on your side. See Trigger from Contact.
Use the direct /run call above when your own code is the trigger (a cron job, a backend event, another service). Use Contact tags when a device or message is the trigger.
Check the result#
Both paths return a 200 as soon as the run is queued. To see what happened:
- Poll the run —
GET https://app.tendrl.com/strand/api/runs/{run_id}returns its - Let the workflow report out — most workflows end by calling a connector (Slack,
- Inspect step by step — the Runs page shows every run with its
status (queued → running → completed/failed), compute units, and duration.
email, an HTTP callback), so you often don't need to poll at all.
per-node input and output.
Responses and errors#
| Status | Meaning |
|---|---|
200 |
Run queued. Not a guarantee the workflow finished — check the run. |
401 |
Missing or invalid API key. |
422 |
Body is missing the required data field. |
403 |
The API key's role lacks run permission, or a plan limit was reached. |
503 |
The run queue is temporarily full. Retry after a short wait; existing runs keep processing. |
Next#
- Review your run results
- Trigger from Contact — device and entity events
- Limits — rate limits, timeouts, and payload ceilings
Tendrl