---
title: "Update a Loop"
method: PATCH
path: "/v3/organizations/{orgId}/reasons/{reasonId}/loops/{loopId}"
tags: ["Loops"]
---

# Update a Loop

`PATCH /v3/organizations/{orgId}/reasons/{reasonId}/loops/{loopId}`

## Request

- Base URL: `https://api.reasonmachines.com`
- URL: `https://api.reasonmachines.com/v3/organizations/{orgId}/reasons/{reasonId}/loops/{loopId}`
- Auth: HTTP bearer

Partial update. A paused Loop resumes from its next slot. `wake_on` replaces everything that wakes the Loop (send it alone, or with `name`, `prompt`, `notification_policy`, `until`, `max_runs` and `model`; a single entry of the kind already waking it changes that in place); a webhook it adds returns its URL and key once, as on create. Switching the Loop on, or onto another Session or model, checks the workspace can fund it (402 `loop_model_not_fundable`). Who sees and changes a Loop follows its Reason: the people who can use the Reason see and change its Loops, except that a teammate's Loop on a team Reason stays that teammate's (the Reason's owner may see and stop it). Workspace owners and admins may pause and delete any Loop.What a Loop runs in (its Session or computer), its Slack channels and what wakes it stay with its creator, since each is checked against their access (403 `loop_creator_only_change`).

<sub>Scope: `automations:write`</sub>

## Request body

- UpdateReasonLoop: Only the fields sent change. `wake_on` replaces what wakes the Loop; send it with only `name`, `prompt`, `notification_policy`, `until`, `max_runs` and `model`.
  - `prompt` string: New instructions for its future wakes.
  - `name` string
  - `enabled` boolean: `false` pauses it; `true` resumes it from its next slot.
  - `schedule` string: A new schedule for a Loop that has one, in the same forms as a `wake_on` schedule.
  - `webhook` object: A webhook Loop's signing secret or condition, changed in place.
    - `signing_secret_name` string, nullable: Name of a stored secret (the Reason's or the caller's) that the sender generated and signs with, such as Stripe's `whsec_` secret. `null` removes it. Without one, senders use the Loop's own key.
    - `condition` string, nullable: Plain words a small model checks each event against before it opens a Session, for example `a bug report or a complaint, not praise`. An unclear answer still wakes the Loop.
  - `wake_on` union[]: Everything 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.
    - union
      - LoopScheduleWake: Wake on a schedule.
        - `schedule` string, required: When it wakes: a one-time ISO 8601 time with `Z` or an offset, a fixed interval such as `every 30 minutes` (60 seconds at least), or a cron expression such as `0 9 * * 1-5`, read in `timezone`.
      - LoopWebhookWake: Wake on each event POSTed to a webhook the Loop gives out (`receiveLoopWebhookEvent`). The URL and its key come back once, in the response that adds it.
        - `webhook` object, required: Options for the webhook the Loop gives out. Send `{}` for none.
          - `signing_secret_name` string, nullable: Name of a stored secret (the Reason's or the caller's) that the sender generated and signs with, such as Stripe's `whsec_` secret. `null` removes it. Without one, senders use the Loop's own key.
          - `condition` string, nullable: Plain words a small model checks each event against before it opens a Session, for example `a bug report or a complaint, not praise`. An unclear answer still wakes the Loop.
      - LoopGithubWake: Wake on a GitHub event in a connected repository.
        - `github` object, required
          - `repo` string, required: `owner/name` of a GitHub repository connected to the workspace; the Loop's runs work in it.
          - `event` 'issue_opened' | 'issue_labeled' | 'issue_closed' | 'pr_opened' | 'pr_reopened' | 'pr_ready_for_review' | 'pr_pushed' | 'pr_closed' | 'pr_review_submitted', required: What on GitHub wakes it. `issue_labeled` and `issue_closed` need `filters.label`; `pr_closed` includes merged pull requests.
          - `filters` object: Only issues or pull requests matching every filter given wake it.
            - `label` string
            - `author` string[]
            - `title_contains` string
      - LoopSlackWake: Wake on Slack messages; the Loop answers as its Reason, one Session per Slack thread.
        - `slack` object, required
          - `channels` string[], required: Slack channel ids, such as `C0123ABCD`, that Reason's Slack app is in.
          - `matches` object[]: What in those channels wakes it: `{"kind":"message"}` any new message (the default), `{"kind":"mention"}`, `{"kind":"keyword","words":[...]}` or `{"kind":"reaction","emoji":[...],"owner_only":false}`.
            - `kind` 'message' | 'mention' | 'keyword' | 'reaction', required
          - `from_users` string[]: Slack user ids; only these people wake it. Empty or absent is anyone.
          - `include_apps` boolean: Other apps' posts wake it too: emails sent to the channel, alerts and integrations.
          - `flag_channel` string, nullable: Slack channel id where the Reason flags things for the team.
          - `condition` string: Plain words a small model checks each event against before it opens a Session, for example `a bug report or a complaint, not praise`. An unclear answer still wakes the Loop.
  - `until` string, date-time, nullable: 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.
  - `max_runs` integer, nullable: How many runs a scheduled Loop makes before it reads `completed`, counted from when it is set. `null` clears it.
  - `execution_mode` 'session' | 'new_session': `session` 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_id` string, uuid, nullable: An existing Session of the caller's that each wake continues. `null` unbinds it.
  - `device_id` string, uuid, nullable: A connected computer of the caller's (`listDevices`) that its runs use; `null` runs them on a Reason cloud machine.
  - `notification_policy` 'important_updates' | 'failed_runs_only': When the Loop tells the person about a run: on important updates (the default) or only when a run fails.
  - `timezone` string: 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.
  - `model` string, nullable: Model 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`.

## Response `200`

The updated Loop, and a webhook it just gave out.

- ReasonLoopWrite: The Loop as saved, and the webhook it just gave out, when it did.
  - `loop` ReasonLoop, required: A Loop: standing work a Reason does, woken by a schedule or an event. Each wake opens or continues a Session of the Reason.
    - `id` string, uuid, required: Loop id. Pass it as `loopId`.
    - `reason_id` string, uuid, required: The Reason the Loop is filed under; its runs are that Reason's Sessions.
    - `name` string, required
    - `prompt` string, required: The instructions each wake follows.
    - `enabled` boolean, required
    - `status` 'active' | 'paused' | 'completed' | 'failed', required: `completed` when a scheduled Loop reached its `until` or `max_runs`; `paused` names why in `paused_reason`.
    - `paused_reason` string, nullable, required: Why the scheduler paused it (for example `out_of_credits` or `repeated_failures`); null when it is not paused or was paused by hand.
    - `trigger_description` string: What wakes it, in words.
    - `wakes_on` object[]
      - `kind` 'schedule' | 'slack' | 'webhook' | 'github', required
      - `description` string, required
    - `schedule` object, nullable, required: Its schedule; null when only events wake it.
      - `kind` 'every' | 'cron' | 'at', required
      - `expression` string, nullable, required
      - `every_seconds` integer, nullable, required
      - `timezone` string, required
      - `next_run_at` string, date-time, nullable, required
    - `event_trigger` object, nullable
      - `kind` 'webhook' | 'github', required
      - `url` string, nullable: For a webhook: where senders POST; null when this server has no public URL. The key is never returned here.
    - `slack` object, nullable, required: The Slack channels that wake it and its health; null when Slack does not.
    - `execution_mode` 'session' | 'new_session', required
    - `session_id` string, nullable, required: The Session it is bound to, when one is.
    - `latest_session_id` string, nullable, required: The Session its most recent wake ran in.
    - `device_id` string, nullable, required
    - `repository` string, nullable, required
    - `model` string, nullable, required
    - `notification_policy` 'important_updates' | 'failed_runs_only', required
    - `until` string, date-time, nullable, required
    - `max_runs` integer, nullable, required
    - `run_count` integer, required
    - `last_run` object, nullable, required
      - `status` string, required
      - `at` string, required
      - `session_id` string, required
    - `created_by_user_id` string, uuid, required: Who created it; its runs are billed as theirs.
    - `viewer_access` object: What the caller may do with it: change what it runs (`edit`), or pause and delete it (`stop`). Present on reads (list and get).
      - `edit` boolean, required
      - `stop` boolean, required
  - `webhook` LoopWebhookKey: The webhook a Loop just gave out, returned once.
    - `url` string, uri, required: Where senders POST events (`receiveLoopWebhookEvent`). Also readable later as `event_trigger.url`.
    - `key` string, required: The webhook's key, shown only in this response: send it as `Authorization: Bearer <key>`, sign the body with it, or append it to the URL as a last path segment. Store it now; it is never readable again.

## Other responses

- `400`: Invalid request.
- `401`: Missing, invalid, or expired key.
- `402`: The workspace's plan or credits can't run this Loop's model (`loop_model_not_fundable`).
- `403`: The key lacks the required scope or role.
- `404`: Resource not found.
- `409`: The change can't apply to the Loop as it is (for example `loop_paused` or `loop_runs_used_up`); the message says what to do.
- `429`: Too many requests. Retry after the number of seconds in the `Retry-After` response header.

## Changes

- **2026-10-26** `c250866428e0`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/ara/apis/reason-api/changes/v3/organizations/:orgId/reasons/:reasonId/loops/:loopId/patch.md)

---

[API](https://skmtc.dev/ara/apis/reason-api.md) - [All operations](https://skmtc.dev/ara/apis/reason-api/llms.txt) - [OpenAPI document](https://skmtc.dev/ara/apis/reason-api/revisions/e97c8704807c?raw)
