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

# Deployments

> Run an agent on a cron schedule.

A **deployment** runs an [agent](/docs/api/agents) automatically on a cron schedule. Each fire creates a new [session](/docs/api/sessions) from a fixed first input (`input_template`), subject to its own budget. Use deployments for recurring, unattended work.

## The deployment object

<ResponseField name="id" type="string">Unique id (e.g. `dep_01H...`).</ResponseField>
<ResponseField name="agent_id" type="string">The agent to run.</ResponseField>
<ResponseField name="agent_version" type="integer | null">Pinned [agent version](/docs/api/agents#the-agent-version-object) each run uses. `null` means every fire tracks the agent's `current_version`.</ResponseField>
<ResponseField name="cron" type="string">Cron expression defining the schedule, read in `timezone`.</ResponseField>
<ResponseField name="timezone" type="string">IANA zone the `cron` is read in (default `"UTC"`), so `0 9 * * 1` stays 09:00 local across a daylight-saving shift.</ResponseField>
<ResponseField name="input_template" type="string">Text used as the session's first input on each run.</ResponseField>
<ResponseField name="budget_micro_usd" type="integer">Per-run session budget, in micro-USD. Always set — it is required at create.</ResponseField>
<ResponseField name="window" type="string">Completion window for scheduled runs.</ResponseField>
<ResponseField name="on_idle" type="string | null">URL POSTed with the `session.idle` envelope when a fire goes idle. A non-null value has a [webhook endpoint](/docs/api/webhooks) behind it, listed by `GET /v1/webhooks` with `events: ["deployment.on_idle"]`; `POST /v1/webhooks/{id}/rotate` returns the secret these deliveries are signed with, and `GET /v1/webhooks/{id}/deliveries` is their log. This field is the authority: if the endpoint behind it is re-pointed elsewhere, the push is refused rather than sent to the other URL.</ResponseField>
<ResponseField name="identity" type="string | null">The [persona](/docs/identity/personas) every fire speaks as, as an `idn_` id. `null` runs each fire as no persona.</ResponseField>
<ResponseField name="enabled" type="boolean">Whether the schedule is active — what you asked for.</ResponseField>
<ResponseField name="schedule_status" type="string">Whether anything will act on it — what you get. `scheduled` (a clock is watching it), `paused` (`enabled: false`), or `unscheduled` (enabled, but no clock runs schedules on this installation, so it will never fire on its own).</ResponseField>
<ResponseField name="next_run_at" type="string | null">Next fire time, and **`null` unless `schedule_status` is `scheduled`** — a date nothing will honour is worse than none. An `unscheduled` deployment still fires on demand with `POST /v1/deployments/{id}/run`.</ResponseField>
<ResponseField name="created_at" type="string">Creation timestamp.</ResponseField>

## Create a deployment

`POST /v1/deployments` → `201 Created`.

<ParamField body="agent_id" type="string" required>The agent to run on schedule.</ParamField>
<ParamField body="agent_version" type="integer">Pin runs to a specific agent version for staged rollout or rollback. Omit to always track the agent's `current_version`.</ParamField>
<ParamField body="cron" type="string" required>Cron expression (e.g. `0 9 * * 1` = Mondays at 09:00), read in `timezone`.</ParamField>
<ParamField body="timezone" type="string">IANA zone the `cron` is read in. Defaults to `UTC`.</ParamField>
<ParamField body="input_template" type="string" required>The input each run sends to its session.</ParamField>
<ParamField body="budget_micro_usd" type="integer" required>Per-run budget in micro-USD. There is no default: an unbudgeted schedule could spend without a ceiling, so it must be stated.</ParamField>
<ParamField body="window" type="string">Completion window (`immediate | priority | loose`). Defaults to the agent's window.</ParamField>
<ParamField body="on_idle" type="string">URL POSTed with the `session.idle` envelope each time a fire goes idle. Defaults to `null`, which pushes nothing. Setting it provisions the webhook endpoint the pushes are delivered through, so they inherit the signature, the retry schedule, the auto-disable and the delivery log.</ParamField>
<ParamField body="identity" type="string">The [persona](/docs/identity/personas) every fire speaks as — an `idn_` id **or** the persona's name, exactly as when creating a [session](/docs/api/sessions). The agent must already hold a grant to it. It is resolved at create and stored as the id, so the response echoes `idn_…` even when you sent a name, and renaming the persona later cannot re-point the schedule. Defaults to `null`: the fire runs as no persona.</ParamField>
<ParamField body="enabled" type="boolean">Start active. Defaults to `true`.</ParamField>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -fsSL https://api.vetta.sh/v1/deployments \
    -H "authorization: Bearer sk_live_..." \
    -H "content-type: application/json" \
    -H "idempotency-key: $(uuidgen)" \
    -d '{
      "agent_id": "agt_01H8XK...",
      "agent_version": 2,
      "cron": "0 9 * * 1",
      "input_template": "Summarize last week'\''s refunds and publish a report.",
      "budget_micro_usd": 3000000,
      "window": "loose"
    }'
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={"system"}
  {
    "id": "dep_01H9EF...",
    "object": "deployment",
    "agent_id": "agt_01H8XK...",
    "agent_version": 2,
    "cron": "0 9 * * 1",
    "input_template": "Summarize last week's refunds and publish a report.",
    "budget_micro_usd": 3000000,
    "window": "loose",
    "timezone": "America/New_York",
    "on_idle": null,
    "identity": null,
    "enabled": true,
    "schedule_status": "scheduled",
    "next_run_at": "2026-08-24T13:00:00Z",
    "created_at": "2026-08-20T17:00:00Z"
  }
  ```
</ResponseExample>

## Retrieve & list

```bash theme={"system"}
GET /v1/deployments/{id}   # retrieve one -> 200
GET /v1/deployments        # list (cursor-paginated) -> 200
```

See [Pagination](/docs/api/pagination).

## Update a deployment

`PATCH /v1/deployments/{id}` → `200 OK`. Scope `agents:write`. A partial update — send only the fields you are changing.

Every field of the deployment is patchable **except `agent_id`**: repointing a schedule at a different agent would silently change what the cron does while keeping its history, so create a new deployment instead.

<ParamField body="cron" type="string">New cron expression. Recomputes `next_run_at`.</ParamField>
<ParamField body="timezone" type="string">IANA zone the cron is read in.</ParamField>
<ParamField body="input_template" type="string">The message each fire starts with.</ParamField>
<ParamField body="agent_version" type="integer">Pin a different agent version.</ParamField>
<ParamField body="budget_micro_usd" type="integer">Per-run cap, in micro-USD.</ParamField>
<ParamField body="on_idle" type="string">The idle-push URL, or `null` to stop pushing — `null` deletes the endpoint behind it and its signing secret. A new URL re-points the endpoint and re-enables it, so this is how a target that auto-disabled after repeated failures is brought back.</ParamField>
<ParamField body="identity" type="string">The persona each fire speaks as, or `null` to run as none. Re-validated against the agent's grants.</ParamField>
<ParamField body="enabled" type="boolean">The schedule switch — the same state [pause and resume](#pause--resume) toggle.</ParamField>

```bash theme={"system"}
curl -fsSL -X PATCH https://api.vetta.sh/v1/deployments/dep_01H9EF... \
  -H "authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{ "cron": "0 9 * * 1-5", "timezone": "Europe/London" }'
```

Returns the full deployment object, including the recomputed `next_run_at` and `schedule_status`.

**Errors** — `validation_failed` (400) for an unparseable cron, an unknown timezone, an attempt to change `agent_id`, or an `identity` the agent does not hold; `not_found` (404), including an `identity` that does not exist.

## Pause & resume

`POST /v1/deployments/{id}/pause` and `POST /v1/deployments/{id}/resume` → `200 OK`. Toggle the schedule without deleting the deployment.

```json Response theme={"system"}
{ "id": "dep_01H9EF...", "enabled": false, "schedule_status": "paused", "next_run_at": null }
```

## Run once, now

`POST /v1/deployments/{id}/run` → `202 Accepted`. Fires the deployment immediately, whatever its schedule says — including while it is `paused` or `unscheduled`. Every fire, scheduled or manual, is an ordinary session carrying this deployment's `deployment_id`.

## List runs

`GET /v1/deployments/{id}/runs` → `200 OK`. Lists the sessions produced by this deployment, newest first. Cursor-paginated.

```json Response theme={"system"}
{
  "data": [
    { "session_id": "ses_01H9GG...", "status": "idle", "started_at": "2026-08-24T09:00:02Z" }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## Delete a deployment

`DELETE /v1/deployments/{id}` → `200 OK`. Removes the schedule. Sessions it already created are retained.

```json Response theme={"system"}
{ "id": "dep_01H9EF...", "object": "deployment", "deleted": true }
```
