> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pinework.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Automation

> An automation starts an agent run on a cron schedule, a GitHub pull request event or a POST to a secret URL.

An automation gives one agent a saved prompt and starts a run each time its trigger fires. Its id looks like `aut_` plus 16 characters. The dashboard calls it a routine. The API and CLI call it an automation. For the steps, see [Schedule a routine](/guides/schedule-a-routine) and [Start a run from a webhook](/guides/send-webhooks).

## States

| State | Meaning | Ends here |
| - | - | - |
| `active` | The trigger fires and starts runs. New automations start here. | No |
| `paused` | Fires are skipped and recorded as skips. | No |
| `archived` | Fires are skipped. Automations are archived, never deleted. | No |

| From | To | Trigger |
| - | - | - |
| `active` | `paused` | `automation pause` |
| `paused` | `active` | `automation resume` |
| `active` or `paused` | `archived` | `automation archive` |
| `archived` | `active` | `automation unarchive` or `automation resume` |
| Any | Any | `automation update --status <state>` |

### Triggers

| `trigger.type` | Fires when | Default `concurrencyPolicy` |
| - | - | - |
| `schedule` | The 5-field cron `cron` matches in the IANA `timezone`. There is no seconds field. | `skip_if_running` |
| `github` | `pull_request.merged`: a pull request merges into the default branch of `repo`. `pull_request.checks_passed`: a task's open pull request goes all green at a new head. | `allow_concurrent` |
| `webhook` | Someone POSTs to the automation's secret URL. The body becomes part of the run's prompt. | `allow_concurrent` |

`concurrencyPolicy` is `skip_if_running` or `allow_concurrent`. With `skip_if_running`, a fire is skipped while an earlier run is still `queued` or `running`.

A `workflow` name makes each GitHub fire start that workflow instead of a run. Only a `github` trigger can carry a `workflow`.

### Skip reasons

A fire that starts no run is recorded as a skip. List them with `GET /api/v1/automations/:automationId/skips`.

| `reason` | Meaning |
| - | - |
| `rate_limited` | The webhook took over 60 calls in an hour. |
| `concurrency` | A run was still going under `skip_if_running`. |
| `paused` | The automation was paused. |
| `archived` | The automation was archived. |
| `stale_head` | The pull request moved past the head the fire named. |
| `verdict_exists` | A Verdict line already sits at that head. |
| `carried` | An APPROVE was carried across a clean merge of main with no run. |

## Fields

<ResponseField name="id" type="string" required>Automation id, `aut_` plus 16 characters.</ResponseField>
<ResponseField name="object" type="string" required>Always `automation`.</ResponseField>
<ResponseField name="workspaceId" type="string" required>Workspace id.</ResponseField>
<ResponseField name="projectId" type="string">Project id. Null when unset. Create and update do not set it.</ResponseField>
<ResponseField name="name" type="string" required>Label.</ResponseField>
<ResponseField name="description" type="string">Markdown description. Null when unset.</ResponseField>
<ResponseField name="status" type="string" required>`active`, `paused` or `archived`.</ResponseField>

<ResponseField name="trigger" type="object" required>
  What fires the automation.

  <Expandable title="trigger">
    <ResponseField name="type" type="string" required>`schedule`, `github` or `webhook`.</ResponseField>
    <ResponseField name="cron" type="string">5-field cron. Only on `schedule`.</ResponseField>
    <ResponseField name="timezone" type="string">IANA timezone. Only on `schedule`.</ResponseField>
    <ResponseField name="event" type="string">`pull_request.merged` or `pull_request.checks_passed`. Only on `github`.</ResponseField>
    <ResponseField name="repo" type="string">`owner/name`, stored lower-case. Only on `github`.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="prompt" type="string" required>The prompt each fire's run starts from.</ResponseField>
<ResponseField name="workflow" type="string">Workflow a GitHub fire starts instead of a run. Null when unset.</ResponseField>
<ResponseField name="agentId" type="string" required>Agent that runs, `agt_` id.</ResponseField>
<ResponseField name="modelId" type="string">Model override. Null means the agent's default.</ResponseField>
<ResponseField name="environment" type="string">`cloud` or `device`. Null means the agent's default.</ResponseField>
<ResponseField name="concurrencyPolicy" type="string" required>`skip_if_running` or `allow_concurrent`.</ResponseField>

<ResponseField name="createdBy" type="object" required>
  Who created it.

  <Expandable title="createdBy">
    <ResponseField name="type" type="string" required>`user`, `agent` or `system`.</ResponseField>
    <ResponseField name="id" type="string">Public id. Null when `type` is `system`.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="lastRun" type="object">
  Most recent run. Null when it never ran.

  <Expandable title="lastRun">
    <ResponseField name="id" type="string" required>Run id.</ResponseField>
    <ResponseField name="status" type="string" required>`queued`, `running`, `succeeded`, `failed` or `cancelled`.</ResponseField>
    <ResponseField name="startedAt" type="string" required>When the automation fired, ISO 8601.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="archivedAt" type="string">Archive time. Null when not archived.</ResponseField>
<ResponseField name="createdAt" type="string" required>ISO 8601.</ResponseField>
<ResponseField name="updatedAt" type="string" required>ISO 8601.</ResponseField>
<ResponseField name="webhookUrl" type="string">The secret webhook URL. Present only on create, rotate, or an update that switched the trigger to `webhook`.</ResponseField>

`run` returns an `automation_fire` object. Its `kind` is `triggered` with a `runId`, or `skipped` with a `reason` of `concurrency` or `not_active`. `runId` is null when no run was admitted.

## CLI

| Command | Does |
| - | - |
| `automation create` | Create an automation with `--cron`, `--event` plus `--repo`, or `--trigger webhook`. |
| `automation list` | List automations. Filter with `--status`, `--agent` or `--q`. |
| `automation get <aut_…>` | Print one automation. |
| `automation update <aut_…>` | Change only the flags you pass. A new trigger replaces the old one whole. |
| `automation run <aut_…>` | Fire once now, outside the schedule. |
| `automation pause <aut_…>` | Stop firing. |
| `automation resume <aut_…>` | Start firing again. |
| `automation archive <aut_…>` | Archive it so it stops firing. |
| `automation unarchive <aut_…>` | Bring an archived automation back. |
| `automation rotate <aut_…>` | Mint a new webhook URL. The old URL stops working at once. |

`--agent` takes `me`, an `agt_` id or an @handle. `--tz` defaults to `UTC`.

```bash theme={null}
pinework automation create --name "Weekday standup" --cron '0 9 * * 1-5' --tz Europe/Berlin \
  --agent agt_3hR7pV2nQ8sLk5Wd --prompt "Summarize yesterday's merged pull requests."
pinework automation create --name "Review merged PRs" --event pr-merged --repo acme/web \
  --agent agt_3hR7pV2nQ8sLk5Wd --prompt "Check the merged change for missing tests."
pinework automation list --status paused --limit 50
pinework automation run aut_8fK2mQ9xLr4TzW1a
```

## API

| Method | Path | Does |
| - | - | - |
| `POST` | `/api/v1/automations` | Create an automation. Returns `201`. |
| `GET` | `/api/v1/automations` | List automations. Query: `status`, `agentId`, `q`, `limit`, `cursor`. |
| `GET` | `/api/v1/automations/runs` | List runs of every automation in the workspace. Query: `status`, `limit`, `cursor`. |
| `GET` | `/api/v1/automations/:automationId` | Retrieve one automation. |
| `POST` | `/api/v1/automations/:automationId` | Update an automation. Unknown body keys are refused. |
| `POST` | `/api/v1/automations/:automationId/pause` | Pause. |
| `POST` | `/api/v1/automations/:automationId/resume` | Resume. |
| `POST` | `/api/v1/automations/:automationId/archive` | Archive. |
| `POST` | `/api/v1/automations/:automationId/unarchive` | Unarchive. |
| `POST` | `/api/v1/automations/:automationId/rotate` | Mint a new webhook URL. |
| `POST` | `/api/v1/automations/:automationId/run` | Fire once now. Returns an `automation_fire` object. |
| `GET` | `/api/v1/automations/:automationId/runs` | List this automation's runs. |
| `GET` | `/api/v1/automations/:automationId/skips` | List fires that started no run, newest first. |
| `POST` | `/api/v1/automation-hooks/:token` | Fire a webhook automation. Needs no auth header. Returns `202`. |

The webhook token is the last segment of `webhookUrl`. The same body sent twice returns the run the first one started.

## Errors

| Code | HTTP | When | What to do |
| - | - | - | - |
| `automation_not_found` | 404 | The id is not in your workspace, or the webhook token is unknown or rotated. | Check the id. Rotate to get a fresh URL. |
| `agent_not_found` | 404 | `agentId` is not an agent in your workspace. | Run `pinework agent list` and use a listed id. |
| `github_not_installed` | 400 | A `github` trigger, but the workspace has no active GitHub App install. | Follow [Connect GitHub](/guides/connect-github) first. |
| `workflow_needs_github_trigger` | 400 | `workflow` is set with a `schedule` or `webhook` trigger. | Use a `github` trigger, or clear `workflow`. |
| `workflow_needs_github_event` | 400 | You ran an automation that starts a workflow by hand. | Let a GitHub event fire it. |
| `not_a_webhook_trigger` | 400 | You rotated an automation without a `webhook` trigger. | Rotate only webhook automations. |

The webhook path also returns `413 payload_too_large` and `429 rate_limit_exceeded`. See [API errors](/reference/api#errors) for the shared codes.

## Limits

* A webhook automation takes 60 POSTs per hour. A rejected call is recorded as a `rate_limited` skip.
* A webhook body can be up to 1 MiB.
* The run sees the first 32 KiB of a webhook body.
* A list page holds 1 to 100 items, 25 by default.
* `q` on the list is at most 100 characters.
* A `workflow` name is at most 64 characters, lower-case letters, digits and underscores.

<CardGroup cols={2}>
  <Card title="Schedule a routine" href="/guides/schedule-a-routine">
    Create, test, pause and archive a scheduled automation.
  </Card>

  <Card title="Start a run from a webhook" href="/guides/send-webhooks">
    Fire an automation with a POST from your own system.
  </Card>

  <Card title="Run" href="/reference/run">
    The run each fire starts.
  </Card>

  <Card title="Workflow" href="/reference/workflow">
    The program a GitHub fire can start instead.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.