Trigger runs and hand work to agents
Start work from your own systems and apps: an automation from a webhook or a schedule, an agent from a message, with no one in Studio. Every start comes back as a run you can follow, with your system named as the actor.
- For
- Developers
- Ways in
- Webhook · Schedule · Agent run · MCP
- Credential
- Machine token or member session
- Comes back
- A run id
01Choose the way in
| You want to | Call | With | Comes back as |
|---|---|---|---|
| Start an automation from your system | POST …/workflows/{workflow_id}?env=, the secure webhook | Machine token | accepted with a correlation_id, and a run |
| Run an automation now from your app | POST …/workflows/{workflow_id}/run?env= | Member session | 202 |
| Run on a timetable | A schedule in the automation, turned on with POST …/schedules/{schedule_id}/enable | Member session | A run each time it fires |
| Hand an agent a task | POST …/agents-config/{agent_config_id}/test | Member session | run_id |
| Let an outside assistant call your agents | The MCP server, in preview | MCP key | A run per call |
Runs · Ways inYour systems, your apps, and your assistants reaching an instance, and the credential each one carries.
02From your system: the secure webhook
Your system holds no member session, so it starts automations through a secure webhook and a machine token. The token belongs to one environment and is attached to the webhook in Studio, so a leaked token can only start the automations it is attached to, in one environment.
- In Studio, open the automation, mark its webhook secure, and attach the environment's machine token.
GET …/workflows-config/{workflow_config_id}/webhook-url?env=gives you the URL for that environment. - From your system, POST a JSON body. The run starts with it as its input.
- Keep the
correlation_idfrom the answer. It joins your request to the run on the record.
curl -s -X POST "$NSTACK_BASE/workflow/api/v1/organizations/$NSTACK_ORG/workflows/$WORKFLOW_ID?env=$ENV_ID" \
-H "Authorization: Bearer $NSTACK_MACHINE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"claim_id":"CLM-20419","event":"fnol.received"}'
const res = await fetch(`${base}/workflow/api/v1/organizations/${org}/workflows/${workflowId}?env=${envId}`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.NSTACK_MACHINE_TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({ claim_id: "CLM-20419", event: "fnol.received" }),
});
if (!res.ok) throw new Error(`webhook refused: ${res.status}`);
const { correlation_id } = await res.json();
res = requests.post(f"{base}/workflow/api/v1/organizations/{org}/workflows/{workflow_id}",
params={"env": env_id}, json={"claim_id": "CLM-20419", "event": "fnol.received"},
headers={"Authorization": f"Bearer {os.environ['NSTACK_MACHINE_TOKEN']}"})
res.raise_for_status()
correlation_id = res.json()["correlation_id"]
{ "status": "accepted", "correlation_id": "5d6e7f80-91a2-4b3c-8d4e-5f6a7b8c9d0e", "message": "Workflow execution triggered" }
- Exactly once. Call with
test=trueto set the run up without starting it, keep theinstance_idit answers, then start it byinstance_id. A second start with the same id answers409 conflictand starts nothing. - On the record. The run names your token as its actor,
machine:{token_id}, so the record shows which system started it. - Errors.
403 machine_token_not_attachedmeans the token is not attached to this webhook for this environment.503 machine_token_unverifiedmeans retry with backoff. The authentication page lists them all.
03Hand an agent a task, headlessly
Send a message to an agent in one environment and you get a run_id back at once. The agent works after the response: it reads the datasets assigned to its version through query_<dataset>, searches knowledge through search_knowledge, and writes its answer to the run. Your code reads the run when it ends.
curl -s -X POST "$NSTACK_BASE/agents/api/v1/organizations/$NSTACK_ORG/agents-config/$AGENT_CONFIG_ID/test" \
-H "Authorization: Bearer $NSTACK_SESSION" -H "Content-Type: application/json" \
-d '{"message":"Prepare the brief for household 1042 ahead of Thursday's review.","env_id":"'"$ENV_ID"'","thread_id":"household-1042-2026-10"}'
const res = await fetch(`${base}/agents/api/v1/organizations/${org}/agents-config/${agentConfigId}/test`, {
method: "POST",
headers: { ...auth, "Content-Type": "application/json" },
body: JSON.stringify({
message: "Prepare the brief for household 1042 ahead of Thursday's review.",
env_id: envId,
thread_id: "household-1042-2026-10",
}),
});
const { run_id } = await res.json();
const run = await followRun(run_id); // see Follow runs
showDraftToAdvisor(run);
res = requests.post(f"{base}/agents/api/v1/organizations/{org}/agents-config/{agent_config_id}/test",
json={"message": "Prepare the brief for household 1042 ahead of Thursday's review.",
"env_id": env_id, "thread_id": "household-1042-2026-10"}, headers=auth)
run_id = res.json()["run_id"]
run = follow_run(run_id) # see Follow runs
show_draft_to_advisor(run)
| Field | Type | Meaning |
|---|---|---|
message | string | The task. |
env_id | UUID, optional | The environment to run in. The run uses the version pinned there, else the latest version, else the saved draft. |
thread_id | string, optional | Continues an earlier conversation. Use one id per case, customer, or ticket. |
execution_report | boolean | True by default. Adds an execution summary to the finished run. GET …/runtime/{correlation_id}/download gives a short-lived link to the full report. |
Agents run in Reads and drafts mode today. The answer is a draft; a person acts on it. Build your integration so the draft lands in front of the right person, with the run id next to it.
A member session with read agents, which the Member role holds. Your service holds a session the same way a browser app does: sign in with your sign-in application and refresh the token before each call.
04On a timetable
An automation carries its schedules in its graph. A schedule starts off. Test the automation in the environment first, then turn the schedule on; a change to its timing turns it off again until someone turns it back on.
/workflow/api/v1/organizations/{org_id}/workflows/{workflow_id}/test?env={env_id}Test the automation in an environment with an optional JSON body. Answers the run's instance id.
NeedsTest and run automations
/workflow/api/v1/organizations/{org_id}/workflows-config/{workflow_config_id}/schedules/{schedule_id}/enableTurn a schedule on.
NeedsUpdate automations, plus manage production for a PROD environment
/workflow/api/v1/organizations/{org_id}/workflows/{workflow_id}/instances?status=The automation's runs, filtered by status.
NeedsRead automations
05From your app, as a member
An app your people sign in to can start work with their session. POST …/workflows/{workflow_id}/run?env= runs an automation now and answers 202. The agent route above takes the same session. Builders do this outside PROD; a PROD run needs the permission to manage production, and your app should ask for a confirmation the way Studio does.
06Which id to keep
| You hold | Read it with | What you get |
|---|---|---|
run_id from an agent run | GET /core/api/v1/organizations/{org_id}/runs/{run_id} | Header, steps, and child runs; …/events for the full sequence |
correlation_id from a webhook | GET …/runs filtered by subject_id and time, or the automation's …/instances | The run the webhook started |
instance_id from a test or a dry run | GET /workflow/api/v1/organizations/{org_id}/workflows/instances/{instance_id} | One run of an automation and its tasks; …/stream-ticket follows it live |
The Follow runs guide shows the polling loop and the state model.
07In design
- In design Machine access to REST, so your system hands agents tasks with a machine credential instead of a member session.
- In design A production run route for agents,
POST …/agents-config/{agent_config_id}/runs, with anIdempotency-Keyheader. - In design Signals, so your system answers a waiting run, such as an approval, through the API.