# triggers

A **trigger** defines what makes a workflow run **automatically**, so you set it once instead of running the workflow by hand. A trigger can be a recurring **schedule** (e.g. every 15 minutes, or every weekday at 09:00) or the **completion of another workflow** (chaining one run after another). Each workflow has at most **one** active trigger.

Use these endpoints to **read**, **set or replace**, and **remove** a workflow's trigger. All endpoints are under `/rest/beta` and require auth via session JWT or `sg_…` API key Bearer token.

### Endpoints

| Method | Path | Purpose |
|---|---|---|
| `GET` | `/rest/beta/workflows/{id}/trigger` | Read the current trigger configuration |
| `PUT` | `/rest/beta/workflows/{id}/trigger` | Set or replace the trigger (idempotent) |
| `DELETE` | `/rest/beta/workflows/{id}/trigger` | Turn the trigger off |

### Trigger type: `time` (schedule)

Run the workflow on a recurring schedule. Use **friendly fields** (recommended) or a **raw cron expression** — the two forms are mutually exclusive; supplying both returns `422`.

#### Friendly fields

```json
{ "type": "time", "frequency": "daily", "time": "09:00", "timezone": "Europe/Berlin" }
```

| Field | Required for | Allowed values |
|---|---|---|
| `frequency` | always | `minutes`, `hourly`, `daily`, `weekly`, `monthly` |
| `every` | `minutes`, `hourly` | `minutes`: `15` or `30`; `hourly`: `1`, `2`, `4`, `6`, `8`, `12` |
| `time` | `daily`, `weekly`, `monthly` | `"HH:MM"` — **minute is always coerced to `:00`** |
| `weekday` | `weekly` | a day name (`sunday` … `saturday`) **or a list** of day names for multiple days, e.g. `["monday","thursday"]` |
| `day_of_month` | `monthly` | `1`–`31` or `"last"` |
| `timezone` | optional | IANA name (e.g. `"America/New_York"`). Defaults to the workflow's current timezone. |

> **`HH:00` constraint:** The minute component of `time` is always silently coerced to `00`. Schedules run at the top of the hour. `"09:45"` is stored and executed as `"09:00"`.

#### Friendly → cron mapping

| Friendly input | Generated cron |
|---|---|
| `minutes`, every `15` | `*/15 * * * *` |
| `minutes`, every `30` | `*/30 * * * *` |
| `hourly`, every `N` (1, 2, 4, 6, 8, 12) | `0 0-23/N * * *` |
| `daily` at hour H | `0 H * * *` |
| `weekly` at hour H on weekday(s) D | `0 H * * D` (sunday=0 … saturday=6; multiple days → `0 H * * 1,2,4,5`) |
| `monthly` at hour H on day DOM | `0 H DOM * *` (`"last"` → `L`) |

#### Reading a trigger (GET) — `cron` is authoritative

`cron` is always the source of truth. The friendly fields (`frequency`, `every`, `time`, `weekday`, `day_of_month`) are a **convenience** filled in only when the schedule maps cleanly to the table above:

- A `weekly` schedule with one day returns `weekday` as a string; with several days it returns a list (cron day lists like `1,2,4,5` and ranges like `1-5` are both parsed).
- **Advanced / compound schedules are not expressed in the friendly fields.** Schedules created in the Sheetgo dashboard can combine things the friendly syntax does not model — an hour window, an hour step, and a weekday restriction together (e.g. `0 6-18/2 * * 1,2,3,4,5` = every 2 hours from 06:00–18:00, Monday–Friday). For these, every friendly field is `null` and only `cron` is populated. Read `cron` to get the exact schedule.

#### Raw cron expression (power-user escape hatch)

```json
{ "type": "time", "cron": "0 9 * * *", "timezone": "UTC" }
```

- Provide `cron` instead of friendly fields. The two forms are mutually exclusive.
- The **minute field** must be `0`, `*/15`, or `*/30`. Any other value returns `422 invalid_trigger`.

### Trigger type: `workflow_finished` (chained execution)

Run this workflow immediately after another workflow completes.

```json
{ "type": "workflow_finished", "after_workflow_id": "abc123" }
```

- `after_workflow_id` is the **parent** workflow whose completion fires this trigger.
- Requires workflow **ownership** (not just edit permission) — see `not_workflow_owner` below.
- Returns `409 circular_trigger` if the relationship would create a trigger loop.

### Response shape

GET and PUT return the full read model; DELETE returns the disabled state.

```json
{
  "active": true,
  "type": "time",
  "frequency": "daily",
  "every": null,
  "time": "09:00",
  "weekday": null,
  "day_of_month": null,
  "cron": "0 9 * * *",
  "timezone": "Europe/Berlin",
  "after_workflow_id": null
}
```

An inactive / never-set trigger returns `{ "active": false, "type": null, ... }` with all other fields `null`.

### curl examples

**Daily time trigger:**
```bash
curl -X PUT /rest/beta/workflows/{id}/trigger \
  -H "Authorization: Bearer sg_..." \
  -H "Content-Type: application/json" \
  -d '{"type":"time","frequency":"daily","time":"09:00","timezone":"Europe/Berlin"}'
```

**15-minute interval:**
```bash
curl -X PUT /rest/beta/workflows/{id}/trigger \
  -H "Authorization: Bearer sg_..." \
  -H "Content-Type: application/json" \
  -d '{"type":"time","frequency":"minutes","every":15}'
```

**workflow_finished trigger:**
```bash
curl -X PUT /rest/beta/workflows/{id}/trigger \
  -H "Authorization: Bearer sg_..." \
  -H "Content-Type: application/json" \
  -d '{"type":"workflow_finished","after_workflow_id":"parent-workflow-id"}'
```

**Turn off a trigger:**
```bash
curl -X DELETE /rest/beta/workflows/{id}/trigger \
  -H "Authorization: Bearer sg_..."
```

### Error codes

All errors return `{ "code": "...", "detail": "..." }`.

| HTTP | `code` | When |
|---|---|---|
| 422 | `invalid_trigger` | Bad/missing fields, both `cron` and friendly fields supplied, or raw-cron minute not `0`, `*/15`, `*/30`. |
| 403 | `permission_denied` | Caller lacks edit or disable permission on the workflow. |
| 403 | `not_workflow_owner` | `workflow_finished` requires workflow ownership. |
| 403 | `plan_upgrade_required` | The account plan does not include this trigger type or feature. |
| 409 | `circular_trigger` | The chosen parent would create a trigger loop. |
| 404 | `workflow_not_found` | The workflow (or parent) does not exist or is not accessible. |
| 500 | — | `{ "status": 0, "error": "internal" }` |


Base URL: `https://dev-api.sheetgo.com`. Start page: https://dev-api.sheetgo.com/rest/reference.md

This page documents every endpoint of the group. Request and response shapes link to model pages; fetch the model pages an endpoint links before building a body.

Guide: [Rate Limiting](https://dev-api.sheetgo.com/rest/guides/rate-limiting.md)

Every endpoint is rate limited; see the guide [Rate Limiting](https://dev-api.sheetgo.com/rest/guides/rate-limiting.md).

<a id="get-rest-beta-workflows-id-trigger"></a>
## GET /rest/beta/workflows/{id}/trigger

**Get a workflow's automation trigger**

Returns the workflow's current trigger — what makes it run without anyone pressing a button.

Only the fields belonging to the active `type` are populated; the rest are null. A workflow with no trigger returns `{"active": false, "type": null}` with HTTP 200, not a 404.

Authentication, any one of: `default`; `Developer Access`; `sheetgo-api-key`.

**Parameters**

| Name | In | Type | Required | Description | Allowed values / default / constraints |
| --- | --- | --- | --- | --- | --- |
| `id` | path | string | yes | Workflow id |  |

**Responses**

- `200` Current trigger configuration. Schema: [TriggerOut](https://dev-api.sheetgo.com/rest/reference/models/TriggerOut.md)
- `401` Authentication failed or token is invalid
- `404` Workflow not found
- `422` Validation Error. Schema: [HTTPValidationError](https://dev-api.sheetgo.com/rest/reference/models/HTTPValidationError.md)

Read next: [HTTPValidationError](https://dev-api.sheetgo.com/rest/reference/models/HTTPValidationError.md), [TriggerOut](https://dev-api.sheetgo.com/rest/reference/models/TriggerOut.md).

<a id="put-rest-beta-workflows-id-trigger"></a>
## PUT /rest/beta/workflows/{id}/trigger

**Set or replace a workflow's automation trigger**

Sets the workflow's trigger, replacing any existing one — this is a full replacement, not a merge.

Pick one of three `type` values:

* `time` — run on a schedule. Use the friendly fields (`frequency`, `every`, `time`, `weekday`, `day_of_month`) **or** a raw `cron`, never both. Schedules run on the hour, so the cron minute must be `0`, `*/15` or `*/30`.
* `workflow_finished` — run when another workflow finishes, named by `after_workflow_id`. Chains that would form a loop are rejected with **409**.
* `form_submission` — run whenever a bound form receives a response.

The response echoes the stored trigger, including the resolved `cron`.

Authentication, any one of: `default`; `Developer Access`; `sheetgo-api-key`.

**Parameters**

| Name | In | Type | Required | Description | Allowed values / default / constraints |
| --- | --- | --- | --- | --- | --- |
| `id` | path | string | yes | Workflow id |  |

**Request body**

Content type `application/json`. Required.

One of three trigger types, discriminated by `type`: `time`, `workflow_finished` or `form_submission`.

Body: [TimeTriggerIn (type = "time")](https://dev-api.sheetgo.com/rest/reference/models/TimeTriggerIn.md) or [WorkflowFinishedTriggerIn (type = "workflow_finished")](https://dev-api.sheetgo.com/rest/reference/models/WorkflowFinishedTriggerIn.md) or [FormSubmissionTriggerIn (type = "form_submission")](https://dev-api.sheetgo.com/rest/reference/models/FormSubmissionTriggerIn.md)

**Responses**

- `200` Trigger set. Schema: [TriggerOut](https://dev-api.sheetgo.com/rest/reference/models/TriggerOut.md)
- `401` Authentication failed or token is invalid
- `403` Permission / plan / owner restriction
- `404` Workflow (or parent) not found
- `409` Circular trigger
- `422` Invalid trigger configuration

Read next: [FormSubmissionTriggerIn](https://dev-api.sheetgo.com/rest/reference/models/FormSubmissionTriggerIn.md), [TimeTriggerIn](https://dev-api.sheetgo.com/rest/reference/models/TimeTriggerIn.md), [TriggerOut](https://dev-api.sheetgo.com/rest/reference/models/TriggerOut.md), [WorkflowFinishedTriggerIn](https://dev-api.sheetgo.com/rest/reference/models/WorkflowFinishedTriggerIn.md).

<a id="delete-rest-beta-workflows-id-trigger"></a>
## DELETE /rest/beta/workflows/{id}/trigger

**Turn off a workflow's automation trigger**

Disables the workflow's trigger, so it runs only on demand. Returns the trigger in its disabled state (`active: false`). Safe to call on a workflow that has no trigger.

Authentication, any one of: `default`; `Developer Access`; `sheetgo-api-key`.

**Parameters**

| Name | In | Type | Required | Description | Allowed values / default / constraints |
| --- | --- | --- | --- | --- | --- |
| `id` | path | string | yes | Workflow id |  |

**Responses**

- `200` Trigger disabled. Schema: [TriggerOut](https://dev-api.sheetgo.com/rest/reference/models/TriggerOut.md)
- `401` Authentication failed or token is invalid
- `403` Permission restriction
- `404` Workflow not found
- `422` Validation Error. Schema: [HTTPValidationError](https://dev-api.sheetgo.com/rest/reference/models/HTTPValidationError.md)

Read next: [HTTPValidationError](https://dev-api.sheetgo.com/rest/reference/models/HTTPValidationError.md), [TriggerOut](https://dev-api.sheetgo.com/rest/reference/models/TriggerOut.md).

---

More: start page https://dev-api.sheetgo.com/rest/reference.md, everything in one file https://dev-api.sheetgo.com/rest/llms-full.txt, OpenAPI https://dev-api.sheetgo.com/rest/openapi.json.
