curl --request POST \
--url https://api.reasonmachines.com/v3/organizations/{orgId}/reasons/{reasonId}/loops \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Morning triage",
"prompt": "Triage new support tickets and post a summary.",
"wake_on": [
{
"schedule": "0 9 * * 1-5"
}
],
"timezone": "America/Los_Angeles"
}
'import requests
url = "https://api.reasonmachines.com/v3/organizations/{orgId}/reasons/{reasonId}/loops"
payload = {
"name": "Morning triage",
"prompt": "Triage new support tickets and post a summary.",
"wake_on": [{ "schedule": "0 9 * * 1-5" }],
"timezone": "America/Los_Angeles"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Morning triage',
prompt: 'Triage new support tickets and post a summary.',
wake_on: [{schedule: '0 9 * * 1-5'}],
timezone: 'America/Los_Angeles'
})
};
fetch('https://api.reasonmachines.com/v3/organizations/{orgId}/reasons/{reasonId}/loops', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.reasonmachines.com/v3/organizations/{orgId}/reasons/{reasonId}/loops",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Morning triage',
'prompt' => 'Triage new support tickets and post a summary.',
'wake_on' => [
[
'schedule' => '0 9 * * 1-5'
]
],
'timezone' => 'America/Los_Angeles'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.reasonmachines.com/v3/organizations/{orgId}/reasons/{reasonId}/loops"
payload := strings.NewReader("{\n \"name\": \"Morning triage\",\n \"prompt\": \"Triage new support tickets and post a summary.\",\n \"wake_on\": [\n {\n \"schedule\": \"0 9 * * 1-5\"\n }\n ],\n \"timezone\": \"America/Los_Angeles\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.reasonmachines.com/v3/organizations/{orgId}/reasons/{reasonId}/loops")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Morning triage\",\n \"prompt\": \"Triage new support tickets and post a summary.\",\n \"wake_on\": [\n {\n \"schedule\": \"0 9 * * 1-5\"\n }\n ],\n \"timezone\": \"America/Los_Angeles\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.reasonmachines.com/v3/organizations/{orgId}/reasons/{reasonId}/loops")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Morning triage\",\n \"prompt\": \"Triage new support tickets and post a summary.\",\n \"wake_on\": [\n {\n \"schedule\": \"0 9 * * 1-5\"\n }\n ],\n \"timezone\": \"America/Los_Angeles\"\n}"
response = http.request(request)
puts response.read_body{
"loop": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"reason_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"prompt": "<string>",
"enabled": true,
"status": "active",
"paused_reason": "<string>",
"schedule": {
"kind": "every",
"expression": "<string>",
"every_seconds": 123,
"timezone": "<string>",
"next_run_at": "2023-11-07T05:31:56Z"
},
"slack": {},
"execution_mode": "session",
"session_id": "<string>",
"latest_session_id": "<string>",
"device_id": "<string>",
"repository": "<string>",
"model": "<string>",
"notification_policy": "important_updates",
"until": "2023-11-07T05:31:56Z",
"max_runs": 123,
"run_count": 123,
"last_run": {
"status": "<string>",
"at": "<string>",
"session_id": "<string>"
},
"created_by_user_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"trigger_description": "<string>",
"wakes_on": [
{
"kind": "schedule",
"description": "<string>"
}
],
"event_trigger": {
"kind": "webhook",
"url": "<string>"
},
"viewer_access": {
"edit": true,
"stop": true
}
},
"webhook": {
"url": "<string>",
"key": "<string>"
}
}{
"error": "prompt_required"
}{
"error": "missing_bearer",
"message": "Authorization required"
}{
"error": "missing_scope",
"required_scope": "sessions:read"
}{
"error": "session_not_found"
}{
"error": {
"type": "rate_limited",
"message": "This API key exceeded its request-rate limit"
}
}Create a Loop
Files a new Loop under the Reason, which must be one of the caller’s own live Reasons (404 reason_not_found otherwise). Give what wakes it in wake_on. A Loop woken by events opens a Session per event; webhook events sharing an X-Webhook-Correlation-Key continue one Session.
A Loop with a webhook returns webhook.url and webhook.key in this response only; store the key now, it is never readable again. The workspace’s plan and credits must be able to run the Loop’s model (402 loop_model_not_fundable).
automations:writecurl --request POST \
--url https://api.reasonmachines.com/v3/organizations/{orgId}/reasons/{reasonId}/loops \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Morning triage",
"prompt": "Triage new support tickets and post a summary.",
"wake_on": [
{
"schedule": "0 9 * * 1-5"
}
],
"timezone": "America/Los_Angeles"
}
'import requests
url = "https://api.reasonmachines.com/v3/organizations/{orgId}/reasons/{reasonId}/loops"
payload = {
"name": "Morning triage",
"prompt": "Triage new support tickets and post a summary.",
"wake_on": [{ "schedule": "0 9 * * 1-5" }],
"timezone": "America/Los_Angeles"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Morning triage',
prompt: 'Triage new support tickets and post a summary.',
wake_on: [{schedule: '0 9 * * 1-5'}],
timezone: 'America/Los_Angeles'
})
};
fetch('https://api.reasonmachines.com/v3/organizations/{orgId}/reasons/{reasonId}/loops', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.reasonmachines.com/v3/organizations/{orgId}/reasons/{reasonId}/loops",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Morning triage',
'prompt' => 'Triage new support tickets and post a summary.',
'wake_on' => [
[
'schedule' => '0 9 * * 1-5'
]
],
'timezone' => 'America/Los_Angeles'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.reasonmachines.com/v3/organizations/{orgId}/reasons/{reasonId}/loops"
payload := strings.NewReader("{\n \"name\": \"Morning triage\",\n \"prompt\": \"Triage new support tickets and post a summary.\",\n \"wake_on\": [\n {\n \"schedule\": \"0 9 * * 1-5\"\n }\n ],\n \"timezone\": \"America/Los_Angeles\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.reasonmachines.com/v3/organizations/{orgId}/reasons/{reasonId}/loops")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Morning triage\",\n \"prompt\": \"Triage new support tickets and post a summary.\",\n \"wake_on\": [\n {\n \"schedule\": \"0 9 * * 1-5\"\n }\n ],\n \"timezone\": \"America/Los_Angeles\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.reasonmachines.com/v3/organizations/{orgId}/reasons/{reasonId}/loops")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Morning triage\",\n \"prompt\": \"Triage new support tickets and post a summary.\",\n \"wake_on\": [\n {\n \"schedule\": \"0 9 * * 1-5\"\n }\n ],\n \"timezone\": \"America/Los_Angeles\"\n}"
response = http.request(request)
puts response.read_body{
"loop": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"reason_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"prompt": "<string>",
"enabled": true,
"status": "active",
"paused_reason": "<string>",
"schedule": {
"kind": "every",
"expression": "<string>",
"every_seconds": 123,
"timezone": "<string>",
"next_run_at": "2023-11-07T05:31:56Z"
},
"slack": {},
"execution_mode": "session",
"session_id": "<string>",
"latest_session_id": "<string>",
"device_id": "<string>",
"repository": "<string>",
"model": "<string>",
"notification_policy": "important_updates",
"until": "2023-11-07T05:31:56Z",
"max_runs": 123,
"run_count": 123,
"last_run": {
"status": "<string>",
"at": "<string>",
"session_id": "<string>"
},
"created_by_user_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"trigger_description": "<string>",
"wakes_on": [
{
"kind": "schedule",
"description": "<string>"
}
],
"event_trigger": {
"kind": "webhook",
"url": "<string>"
},
"viewer_access": {
"edit": true,
"stop": true
}
},
"webhook": {
"url": "<string>",
"key": "<string>"
}
}{
"error": "prompt_required"
}{
"error": "missing_bearer",
"message": "Authorization required"
}{
"error": "missing_scope",
"required_scope": "sessions:read"
}{
"error": "session_not_found"
}{
"error": {
"type": "rate_limited",
"message": "This API key exceeded its request-rate limit"
}
}Authorizations
Your Reason API key from Settings › API keys. New keys use reason_; legacy ara_ keys remain accepted. Keys are capability-scoped: run, mcp:read, mcp:write, secrets:read, secrets:write, sessions:read, sessions:debug, knowledge:read, memory:read, memory:write, skills:read, skills:write, repos:read, repos:write, reviews:read, reviews:write, deployment:read, analytics:read, org:read, org:write, attachments:read, attachments:write, guardrails:read, guardrails:write, network:read, network:write, automations:read, automations:write, agent_auth:read. network:read and network:write are opt-in, excluded from default keys. network:read reads the saved workspace network policy; network:write replaces custom allowed hosts and requires a current organization owner/admin. Legacy project PATCH requests containing network_allowed_hosts also require network:write plus their existing run scope and project management permission; saved project hosts do not affect current session egress. Only owners/admins can mint or manage keys with network:write; keys cannot grant themselves more authority. See /docs/network-policies for replacement semantics and runtime applicability. mcp:write manages MCP server configuration only; it does not authorize remote MCP-tool execution. sessions:read reads sessions, including the assistant, reasoning and tool activity in their events. sessions:debug is privileged: only for organization owners/admins, it expands session events to the full diagnostic projection (diagnostic event kinds, status text and raw event metadata).
Path Parameters
Organization id or slug. Resolve it with GET /v3/self.
The Reason id, returned as id by listReasons and createReason.
Body
A new Loop filed under the Reason. It is the caller's: its runs act as the Reason, are billed to the workspace as the caller's, and need the caller's access to any Session, computer, repository or Slack channel it names.
The instructions the Reason follows on each wake. An event's body reaches it as untrusted data, never as instructions.
1 - 100000Everything that wakes the Loop: at most one schedule, one webhook and one Slack listener, and GitHub events on one repository. A Loop woken by several things at once is in early access for some workspaces (403 wake_on_unavailable elsewhere). On update it replaces what wakes the Loop.
1 - 8 elementsWake on a schedule.
- Option 1
- Option 2
- Option 3
- Option 4
Show child attributes
Show child attributes
[
{ "schedule": "0 9 * * 1-5" },
{ "webhook": {} }
]
Label in the Reason's Loops list. Defaults to the start of prompt.
1 - 200Send false to create it paused.
End of a scheduled Loop: no wake after this time, then it reads completed. null clears it. A Loop woken by events has no end of its own.
How many runs a scheduled Loop makes before it reads completed, counted from when it is set. null clears it.
1 <= x <= 100000session continues one Session across wakes, the one in session_id or else one of the Loop's own that its first wake opens; new_session opens a fresh Session on every wake. A Loop woken by events opens a Session per event (events sharing a correlation key continue one), so it takes neither.
session, new_session An existing Session of the caller's that each wake continues. null unbinds it.
A connected computer of the caller's (listDevices) that its runs use; null runs them on a Reason cloud machine.
When the Loop tells the person about a run: on important updates (the default) or only when a run fails.
important_updates, failed_runs_only IANA time zone a cron schedule is read in, such as America/Los_Angeles. Defaults to UTC, or keeps the Loop's own on update.
1 - 120Model its runs use, as a Session model id. null or auto runs it on the model of the Session it continues, or the workspace's. An id the workspace cannot pin returns 400 invalid_model.
1 - 200Response
The new Loop, and its webhook's URL and key when it has one.
The Loop as saved, and the webhook it just gave out, when it did.

