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

# Automations

> Start agent sessions on a schedule, when a Slack, GitHub, or custom webhook event arrives, or by hand, with the same agent version, environment, prompt, and credentials every time.

An automation saves everything a session needs: a pinned agent version, an environment, an opening prompt, and run defaults such as credentials, files, and memory. Its **triggers** decide when it starts a session. Every start is recorded as a **run**, so you can see why each session began and what it was given.

The samples assume the `rl` client from [Client setup](/managed-agents/api#client-setup).

## Before you begin

* Creating automations and event sources needs create permission, changing, pausing, and resuming them needs update, and deleting needs delete. Organization developers and admins have all of them. See [Organizations and roles](/managed-agents/organizations-and-roles).
* You need an agent version and a verified environment. See [Agents](/managed-agents/agents) and [Environments](/managed-agents/environments).
* For Slack, GitHub, or webhook triggers you need an **event source**. See [Receive events](#receive-events).

## How automations run

```mermaid theme={"theme":"css-variables"}
flowchart LR
  schedule["Schedule"] --> automation["Automation"]
  source["Event source: Slack, GitHub, or webhook"] --> automation
  manual["Run now"] --> automation
  automation --> run["Run: pending"]
  run -->|"session created"| created["Run: created, with session_id"]
  run -->|"could not start"| failed["Run: failed, with error"]
```

* **Trigger types:** `schedule`, `slack`, `github`, and `webhook`. You can also start a run by hand at any time, even while the automation is paused.
* **New automations start paused.** Test one with **Run now**, then resume it so its triggers can start runs.
* **Everything is frozen per run.** A run records the automation, trigger, and event exactly as they were, and the session's opening message. Editing the automation later doesn't change runs already started.
* **The opening message** is your saved prompt. For an event run, it's followed by `Event for this run:` and the event as JSON. For a manual run with a payload, it's followed by `Input for this run:` and the payload.

## Create an automation

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Automations**, then click **New automation**.
    2. Name it, choose the agent, its version, and an environment.
    3. Under **Agent instructions**, write what the agent should do each time.
    4. Add triggers: **Schedule**, **Slack**, **GitHub**, or **Webhook**. With none, the automation runs only with **Run now**.
    5. Optionally set **Credential access**, **Files**, **Memory**, and **Run metadata**.
    6. Click **Create automation**. It starts paused.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const automation = await rl.managedAgents.createAutomation({
      'Idempotency-Key': 'weekday-triage-v1',
      body: {
        displayName: 'Weekday triage',
        agentId: '5f0c2a1e-8b7d-4c3a-9e21-6d4f0b9a7c55',
        agentVersionId: 'c1a94e07-2f6b-4d18-b3a5-0e7d8c6f4a21',
        environmentId: '0d5e8a2c-6f13-4b97-a4e0-3c7f9b1d5e62',
        initialPrompt: {
          type: 'literal',
          text: 'Triage issues opened since yesterday and write triage.md.',
        },
        runDefaults: { metadata: { team: 'platform' } },
        triggers: [
          {
            type: 'schedule',
            triggerId: '3f7a1c92-5e48-4b06-9d21-8c4e6a0b2f73',
            enabled: true,
            schedule: { cron: '0 9 * * 1-5', timezone: 'America/New_York' },
          },
        ],
      },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/automations' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -H 'Idempotency-Key: weekday-triage-v1' \
      -d '{
        "displayName": "Weekday triage",
        "agentId": "5f0c2a1e-8b7d-4c3a-9e21-6d4f0b9a7c55",
        "agentVersionId": "c1a94e07-2f6b-4d18-b3a5-0e7d8c6f4a21",
        "environmentId": "0d5e8a2c-6f13-4b97-a4e0-3c7f9b1d5e62",
        "initialPrompt": {"type": "literal", "text": "Triage issues opened since yesterday and write triage.md."},
        "runDefaults": {"metadata": {"team": "platform"}},
        "triggers": [{
          "type": "schedule",
          "triggerId": "3f7a1c92-5e48-4b06-9d21-8c4e6a0b2f73",
          "enabled": true,
          "schedule": {"cron": "0 9 * * 1-5", "timezone": "America/New_York"}
        }]
      }'
    ```
  </Tab>
</Tabs>

The response is `201` with the whole automation and an `ETag` header:

```json theme={"theme":"css-variables"}
{
  "automationId": "a3d8f6b2-1c47-4e95-8b20-7f5c9e1d3a64",
  "displayName": "Weekday triage",
  "agentId": "5f0c2a1e-8b7d-4c3a-9e21-6d4f0b9a7c55",
  "agentVersionId": "c1a94e07-2f6b-4d18-b3a5-0e7d8c6f4a21",
  "environmentId": "0d5e8a2c-6f13-4b97-a4e0-3c7f9b1d5e62",
  "initialPrompt": { "type": "literal", "text": "Triage issues opened since yesterday and write triage.md." },
  "runDefaults": { "metadata": { "team": "platform" } },
  "triggers": [
    {
      "type": "schedule",
      "triggerId": "3f7a1c92-5e48-4b06-9d21-8c4e6a0b2f73",
      "enabled": true,
      "schedule": {
        "cron": "0 9 * * 1-5",
        "timezone": "America/New_York",
        "overlapPolicy": "skip",
        "catchupWindowSeconds": 900,
        "jitterSeconds": 10
      }
    }
  ],
  "status": "paused",
  "revision": 1,
  "createdAt": "2026-09-25T11:02:31Z",
  "updatedAt": "2026-09-25T11:02:31Z"
}
```

* `createAutomation` requires an `Idempotency-Key`. A retry with the same key returns the same automation instead of a second one.
* Every trigger needs a `triggerId` you generate, a UUID that stays the same when you edit the trigger later.
* The agent version, environment, event sources, vaults, files, and memory stores are all checked on create. A reference that doesn't exist or doesn't fit refuses the create with `422`.

## Choose triggers

An automation has up to 20 triggers, and a run starts when any enabled one matches. Turn a trigger off with `enabled: false` to keep it without it starting runs.

### Schedule

| Field                  | Meaning                                                                                                                                                |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `cron`                 | A five-field cron expression, such as `0 9 * * 1-5` for 9:00 on weekdays. The console also offers **Hourly**, **Daily**, **Weekdays**, and **Weekly**. |
| `timezone`             | The IANA time zone the expression is read in, such as `Europe/London`.                                                                                 |
| `overlapPolicy`        | What happens when the previous run's session is still going. See below. The default is `skip`.                                                         |
| `catchupWindowSeconds` | How late a missed occurrence can still run, from 10 seconds to a year. The default is 900, 15 minutes.                                                 |
| `jitterSeconds`        | Up to this many seconds of delay, from 0 to 540, so many schedules don't all start on the same second. The default is 10; 0 turns it off.              |
| `startAt`, `endAt`     | Optional times the schedule starts and stops, inclusive.                                                                                               |

| `overlapPolicy` | Console label                   | Behavior                                                       |
| --------------- | ------------------------------- | -------------------------------------------------------------- |
| `skip`          | **Skip the next run**           | The occurrence is skipped.                                     |
| `buffer_one`    | **Queue one run**               | One occurrence waits and runs when the previous finishes.      |
| `buffer_all`    | **Queue every run**             | Every occurrence waits its turn.                               |
| `allow_all`     | **Run in parallel**             | Occurrences run at the same time.                              |
| `cancel_other`  | **Cancel the previous session** | The running session is cancelled, and the new occurrence runs. |

Read `getAutomationStatus` to see each schedule's `nextRunAt`, `upcomingRuns`, `lastRunAt`, and counts of occurrences skipped for overlap or missed outside the catch-up window. `synchronization` is `synced` once your saved schedule is in effect.

### Slack

Starts a run for Slack messages. It needs a Slack event source: connect Slack on the [Integrations](/managed-agents/integrations) page, or add your own Slack app as a source.

| Field                  | Meaning                                                                                                                                   |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `eventSourceId`        | The Slack source.                                                                                                                         |
| `events`               | `message`, `app_mention`, or both.                                                                                                        |
| `routing.channelIds`   | Channels to accept. An empty list accepts every channel. The console picks channels the Slack app is a member of.                         |
| `routing.workspaceIds` | Workspaces to accept. An empty list accepts every workspace the source receives.                                                          |
| `botEvents.mode`       | `ignore` to skip messages from other Slack apps, or `allow_external` to accept them. Messages from the source's own app never start runs. |
| `allowSharedChannels`  | `true` to accept channels shared with other organizations. The default is `false`.                                                        |
| `filters`              | Optional `textContains`, `textExcludes`, `textMatches` (a regular expression), and `ignoreThreadReplies`. Text matching ignores case.     |

To list channels for a connected Slack workspace, call `getIntegrationSlackChannelCatalog` with the connection id. It returns up to 1,000 channels the Slack app is a member of. Invite the app to a channel to make it listable.

### GitHub

Starts a run for GitHub webhook events. It needs a GitHub event source: connect GitHub on the [Integrations](/managed-agents/integrations) page, or add a repository or organization webhook as a source.

| Field           | Meaning                                                                                                                                                                                                                                                                                                                                                        |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `eventSourceId` | The GitHub source.                                                                                                                                                                                                                                                                                                                                             |
| `events`        | GitHub event names, such as `pull_request`, `issues`, `issue_comment`, `push`, or `workflow_run`.                                                                                                                                                                                                                                                              |
| `filters`       | Optional conditions, all of which must match: `repositories` (`owner/name`), `actions` (such as `opened` or `labeled`), `branches` and `baseBranches` (globs), `labelsAny`, `labelsNone`, `addedLabels`, `senders`, `ignoreBots`, `excludeDrafts`, `merged`, `commentOn` (`pull_request` or `issue`), `reviewStates`, `conclusions`, and comment text filters. |

```json theme={"theme":"css-variables"}
{
  "type": "github",
  "triggerId": "8b2e4d71-6c39-4a05-9f18-3d7a5c1e9b42",
  "enabled": true,
  "eventSourceId": "e1c7a3f9-4b26-4d80-a5e3-9f2b6d8c1a07",
  "events": ["pull_request"],
  "filters": {
    "repositories": ["acme/api"],
    "actions": ["opened", "synchronize"],
    "excludeDrafts": true,
    "ignoreBots": true
  }
}
```

### Webhook

Starts a run for deliveries to a custom webhook source. Add `filters.fieldEquals` to start runs only for some deliveries. Each condition names a dot-separated `path` into the JSON body, such as `data.level`, and the `value` it must equal, written as JSON text without quotes.

```json theme={"theme":"css-variables"}
{
  "type": "webhook",
  "triggerId": "5d9c1e37-2a84-4f60-b7c3-1e8a4d6f9b25",
  "enabled": true,
  "eventSourceId": "7a4e2c18-9d53-4b61-8f07-2c6e9a3d5b14",
  "filters": { "fieldEquals": [{ "path": "data.level", "value": "critical" }] }
}
```

## Set run defaults

`runDefaults` applies to every run, whether a trigger or a person starts it.

| Field            | Meaning                                                                                                                                                                                       |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `metadata`       | String key/value pairs copied onto every session, for example to find them later. `null` for none.                                                                                            |
| `vaultIds`       | Vaults each session can use. Leave it out to use the agent's default vaults. An empty list grants no vault access.                                                                            |
| `credentialRefs` | Specific credentials within `vaultIds`. Leave it out to grant everything in those vaults.                                                                                                     |
| `resources`      | Files from your [library](/managed-agents/files), each with a `fileId` and a `mountPath` under the session's files directory, such as `/data/input.csv`.                                      |
| `memoryStores`   | Up to 7 curated [memory stores](/managed-agents/memory#attach-curated-stores-to-sessions), each with `access` and optional `instructions`. The agent's learned memory is always attached too. |

## Resume, pause, and run by hand

Pause, resume, replace, and delete need the automation's current ETag in `If-Match`. It's `"revision-N"`, where `N` is the automation's `revision`, and it's also returned in the `ETag` header of every read. A stale value returns `412 precondition_failed`; read the automation again and retry.

<Tabs>
  <Tab title="Console">
    1. In **Automations**, open the automation.
    2. Click **Run now** to start one run. You can add an optional **JSON payload**.
    3. Click **Resume** to let its triggers start runs, or **Pause** to stop them.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const run = await rl.managedAgents.runAutomation({
      automationId: 'a3d8f6b2-1c47-4e95-8b20-7f5c9e1d3a64',
      'Idempotency-Key': 'weekday-triage-test-1',
      body: { payload: { since: '2026-09-24' } },
    });
    const active = await rl.managedAgents.resumeAutomation({
      automationId: 'a3d8f6b2-1c47-4e95-8b20-7f5c9e1d3a64',
      'If-Match': '"revision-1"',
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/automations/a3d8f6b2-1c47-4e95-8b20-7f5c9e1d3a64/runs' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -H 'Idempotency-Key: weekday-triage-test-1' \
      -d '{"payload": {"since": "2026-09-24"}}'
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/automations/a3d8f6b2-1c47-4e95-8b20-7f5c9e1d3a64/resume' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'If-Match: "revision-1"'
    ```
  </Tab>
</Tabs>

`runAutomation` requires an `Idempotency-Key` and returns the run:

```json theme={"theme":"css-variables"}
{
  "automationRunId": "d6b1f8a3-7e42-4c95-a0d3-5b9e2f7c1a86",
  "automationId": "a3d8f6b2-1c47-4e95-8b20-7f5c9e1d3a64",
  "triggerType": "manual",
  "status": "pending",
  "statusUrl": "/managed-agents/v1/automation-runs/d6b1f8a3-7e42-4c95-a0d3-5b9e2f7c1a86",
  "eventContext": { "payload": { "since": "2026-09-24" } },
  "effectiveInitialMessage": "Triage issues opened since yesterday and write triage.md.\n\nInput for this run:\n{\"since\":\"2026-09-24\"}",
  "automationSnapshot": { "displayName": "Weekday triage" },
  "promptSnapshot": { "type": "literal", "text": "Triage issues opened since yesterday and write triage.md." },
  "createdAt": "2026-09-25T11:05:12Z",
  "updatedAt": "2026-09-25T11:05:12Z"
}
```

**What success means:** the run moves from `pending` to `created`, and `sessionId` names the session it started. Follow that session like any other; see [Sessions](/managed-agents/sessions). A run that couldn't start a session ends `failed` with an `error` that has a `code`, a `message`, the `field` or `resourceId` responsible when there is one, and `retryable`.

Resume returns the automation with `status` `active`. Pausing stops triggers from starting runs; **Run now** still works.

## See run history

<Tabs>
  <Tab title="Console">
    Open the automation to see its runs, each with its **Trigger**, when it started, and **Session creation**. Filter by status, trigger, or date, and click a run for its **Run detail** and a link to its session.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const { items, nextCursor } = await rl.managedAgents.listAutomationRuns({
      automationId: 'a3d8f6b2-1c47-4e95-8b20-7f5c9e1d3a64',
      status: 'failed',
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/automation-runs?automationId=a3d8f6b2-1c47-4e95-8b20-7f5c9e1d3a64&status=failed' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

Runs come back newest first with `total` and `nextCursor`. Filter by `automationId`, `triggerId`, `eventSourceId`, `triggerType`, `status`, `createdAtGte`, and `createdAtLt`. `getAutomationRun` returns one run with its frozen `automationSnapshot`, `triggerSnapshot`, `sourceSnapshot`, `promptSnapshot`, and `eventContext`. For event runs, `providerDeliveryId` identifies the delivery, and the same delivery never starts the same trigger twice.

## Change or delete an automation

* **`replaceAutomation`** replaces the whole automation, including its trigger set, and keeps its `status`. Send every field, with `If-Match`. Keep each trigger's `triggerId` to keep its identity.
* **`deleteAutomation`** deletes the automation and its triggers, with `If-Match`, and returns `204`. Runs already started keep their records.
* **`listAutomations`** lists automations newest first, without prompts or triggers. `getAutomation` returns the whole automation.

## Receive events

An **event source** is an endpoint that receives events and checks they're genuine. Automations listen to it through Slack, GitHub, and webhook triggers. One source can serve many automations.

| Source type (`type`) | Receives                     | Created by                                                                               |
| -------------------- | ---------------------------- | ---------------------------------------------------------------------------------------- |
| `slack_events_api`   | Slack Events API callbacks.  | Connecting Slack on **Integrations**, or you, for your own Slack app.                    |
| `github_webhook`     | GitHub webhook deliveries.   | Connecting GitHub on **Integrations**, or you, for a repository or organization webhook. |
| `custom_webhook`     | JSON from your own services. | You.                                                                                     |

Sources created by connecting an account are **connection-managed**: they start active, and you pause and resume them from the list, but they're replaced or deleted with their connection. Sources you create start paused.

### Store the signing secret

Slack, GitHub, and signed custom sources check every delivery against a signing secret. Store it first as a **Webhook signing secret** credential (`credential_type: "webhook_secret"`) in a vault. It's used only to check deliveries and never reaches a sandbox.

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Credential vaults**, open a vault, and click **Add credential**.
    2. Set **Type** to **Webhook signing secret**, paste the secret into **Signing secret**, and save.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const secret = await rl.managedAgents.createVaultCredential({
      vault_id: '26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31',
      body: {
        credential_type: 'webhook_secret',
        display_name: 'Alerts webhook secret',
        secret_value: process.env.ALERTS_WEBHOOK_SECRET!,
      },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    jq -n --arg secret "$ALERTS_WEBHOOK_SECRET" '{
      credential_type: "webhook_secret",
      display_name: "Alerts webhook secret",
      secret_value: $secret
    }' | curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/vaults/26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31/credentials' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      --data-binary @-
    ```
  </Tab>
</Tabs>

### Create a source

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Event sources**, then create a source.
    2. Name it and choose the **Provider protocol**: **Slack Events API**, **GitHub webhook**, or **Custom webhook**.
    3. Under **Verification**, choose the **Vault** and **Credential** holding the signing secret. For a custom webhook, set the **Signature header** and optional **Signature prefix**, or choose **Accept unsigned callbacks**.
    4. Save, copy the **Callback URL**, and paste it at the provider.
    5. Resume the source when you're ready to receive events.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const source = await rl.managedAgents.createEventSource({
      'Idempotency-Key': 'alerts-source-v1',
      body: {
        type: 'custom_webhook',
        displayName: 'Production alerts',
        delivery: {
          kind: 'webhook',
          verification: {
            type: 'hmac_sha256',
            signatureHeader: 'X-Signature',
            signaturePrefix: 'sha256=',
            signedParts: [{ kind: 'body' }],
            verificationCredential: {
              vaultId: '26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31',
              credentialId: '61b8e4c2-3a97-4d15-b0f6-8e2c5a9d7b43',
            },
          },
        },
      },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/event-sources' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -H 'Idempotency-Key: alerts-source-v1' \
      -d '{
        "type": "custom_webhook",
        "displayName": "Production alerts",
        "delivery": {
          "kind": "webhook",
          "verification": {
            "type": "hmac_sha256",
            "signatureHeader": "X-Signature",
            "signaturePrefix": "sha256=",
            "signedParts": [{"kind": "body"}],
            "verificationCredential": {
              "vaultId": "26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31",
              "credentialId": "61b8e4c2-3a97-4d15-b0f6-8e2c5a9d7b43"
            }
          }
        }
      }'
    ```
  </Tab>
</Tabs>

The response is `201` with the source, paused:

```json theme={"theme":"css-variables"}
{
  "eventSourceId": "7a4e2c18-9d53-4b61-8f07-2c6e9a3d5b14",
  "type": "custom_webhook",
  "displayName": "Production alerts",
  "status": "paused",
  "delivery": {
    "kind": "webhook",
    "webhookUrl": "/agents/v1/event-sources/7a4e2c18-9d53-4b61-8f07-2c6e9a3d5b14/events",
    "verification": {
      "type": "hmac_sha256",
      "signatureHeader": "X-Signature",
      "signaturePrefix": "sha256=",
      "signedParts": [{ "kind": "body" }],
      "verificationCredential": {
        "vaultId": "26d4b1a8-73c9-4f60-8a15-9e2c7b5d4f31",
        "credentialId": "61b8e4c2-3a97-4d15-b0f6-8e2c5a9d7b43"
      }
    }
  },
  "revision": 1,
  "createdAt": "2026-09-25T11:20:44Z",
  "updatedAt": "2026-09-25T11:20:44Z"
}
```

For a Slack or GitHub source, send `type` `slack_events_api` or `github_webhook` and a `delivery` of `{"kind": "webhook", "verificationCredential": {...}}` naming the Slack signing secret or the GitHub webhook secret.

`webhookUrl` is a path. The full URL to give the provider is the console's address followed by that path, `https://recursion.labelbox.com/agents/v1/event-sources/<eventSourceId>/events`. The console's **Callback URL** copies it whole.

### Send a custom webhook delivery

Send a JSON body with `POST` to the callback URL. For an `hmac_sha256` source, compute the HMAC-SHA256 of the exact body bytes, keyed with your signing secret, and send it as lowercase hexadecimal in the signature header, after the prefix. Sign the bytes you send; re-serializing the JSON after signing breaks the signature.

```http theme={"theme":"css-variables"}
POST /agents/v1/event-sources/7a4e2c18-9d53-4b61-8f07-2c6e9a3d5b14/events HTTP/1.1
Content-Type: application/json
X-Signature: sha256=5b1c9e0f3a7d2b8e4c6f1a9d3e7b2c5f8a0d4e6b9c1f3a5d7e2b8c4f6a0d9e1b

{"data":{"level":"critical","service":"billing"}}
```

A delivery that's accepted returns `200` with `{"ok": true}`. The same signed body is treated as the same delivery, so a retry doesn't start a second run. For an unsigned source, send an `X-Webhook-Id` or `Idempotency-Key` header to name the delivery.

| Response                          | Meaning                                                       |
| --------------------------------- | ------------------------------------------------------------- |
| `200`                             | Accepted. Matching triggers on active automations start runs. |
| `401 unauthorized`                | The signature didn't match.                                   |
| `400 invalid_request`             | The body isn't JSON, or the request is malformed.             |
| `404 not_found`                   | The source doesn't exist or was deleted.                      |
| `413 payload_too_large`           | The body is over 1 MiB.                                       |
| `429 rate_limited`                | Too many deliveries. Retry after `Retry-After`.               |
| `503 event_admission_unavailable` | The delivery couldn't be recorded. Retry.                     |

### Manage sources

* **`getEventSourceStatus`** reports `health` and how many automations use the source (`triggerCount`), plus the last delivery's time and whether it was `admitted` or `ignored`. `health` is `never_received`, `healthy`, `paused`, `connection_unavailable`, or `setup_required`. `healthy` means a delivery has arrived, not that the provider can reach the URL right now.
* **`pauseEventSource`** and **`resumeEventSource`** stop and restart deliveries from reaching triggers, with `If-Match`. Triggers that use the source are left as they are.
* **`replaceEventSource`** replaces a source you created, with `If-Match`, and keeps its status. Saving doesn't resume a paused source.
* **`deleteEventSource`** deletes a source you created, with `If-Match`, only when no automation uses it.

## What can go wrong

| Code or symptom                                    | Cause                                                                           | Fix                                                                                                          |
| -------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `400 invalid_request` on `Idempotency-Key`         | Create and run need one.                                                        | Send an `Idempotency-Key` that names the work.                                                               |
| `422 automation_reference_invalid`                 | The agent version or environment doesn't exist or can't be used.                | Choose a live agent version and a verified environment.                                                      |
| `422 automation_event_source_invalid`              | A trigger's event source is missing or the wrong type for the trigger.          | Use a Slack source for a Slack trigger, and so on.                                                           |
| `422 event_source_verification_credential_invalid` | The verification credential isn't a live `webhook_secret` in your organization. | Store the secret as a **Webhook signing secret**.                                                            |
| `428 precondition_required`                        | `If-Match` is missing.                                                          | Send `If-Match: "revision-N"` from your last read.                                                           |
| `412 precondition_failed`                          | Someone changed it since you read it.                                           | Read it again and retry with the new ETag.                                                                   |
| `409 conflict` deleting a source                   | Automations still use it.                                                       | Remove it from their triggers first.                                                                         |
| A schedule never runs                              | The automation or trigger is paused or off.                                     | Resume the automation, and check `getAutomationStatus`.                                                      |
| A run is `failed`                                  | The session couldn't start.                                                     | Read the run's `error`. If `retryable` is `true`, run it again; otherwise fix the named `field` or resource. |
| No runs from a webhook                             | The source is paused, the signature failed, or the filters didn't match.        | Check `getEventSourceStatus` and the trigger's filters.                                                      |
| `health` is `setup_required`                       | The connected GitHub app needs its event permissions approved.                  | Approve the permission update in GitHub, then check the connection on **Integrations**.                      |

For every error code, see [Errors](/managed-agents/errors).

## Limits

| Limit                                 | Value                                      |
| ------------------------------------- | ------------------------------------------ |
| Triggers per automation               | 20                                         |
| Opening prompt                        | 65,536 characters                          |
| Automation name                       | 256 characters                             |
| Vaults in run defaults                | 50                                         |
| Credentials in run defaults           | 500                                        |
| Files in run defaults                 | 500                                        |
| Curated memory stores in run defaults | 7                                          |
| Schedule catch-up window              | 10 seconds to one year, default 15 minutes |
| Schedule jitter                       | 0 to 540 seconds, default 10               |
| Slack channels per trigger            | 100                                        |
| GitHub repositories per trigger       | 100                                        |
| Webhook field conditions per trigger  | 20                                         |
| Custom webhook body                   | 1 MiB                                      |

See [Limits](/managed-agents/limits) for every other limit.

## Next steps

<CardGroup cols={2}>
  <Card title="Integrations" href="/managed-agents/integrations">
    Connect Slack and GitHub to get event sources.
  </Card>

  <Card title="Memory" href="/managed-agents/memory">
    Give automated runs curated memory.
  </Card>

  <Card title="Files" href="/managed-agents/files">
    Mount the same files into every run.
  </Card>

  <Card title="Sessions" href="/managed-agents/sessions">
    Follow the sessions automations start.
  </Card>
</CardGroup>
