# Versions

> Task drafts, publish, rollback and version history: what POST /v1/tasks/{task_id}/publish freezes and what runs and email batches use.

A task has a **draft** you edit and **published versions** that run. Every write — dashboard, API, MCP or TaskCoPilot — lands on the draft; runs and [email batches](https://docs.talqing.com/email/overview.md) use the published version until you publish again. The lifecycle matches [agent versions](https://docs.talqing.com/agents/versions.md).

```bash
curl -sS -X POST https://api.talqing.com/v1/tasks/0b7d4c92-3e51-4a86-9f2d-71c0a4e58b13/publish \
  -H "Authorization: Bearer $TALQING_API_KEY"
```

```json
{
  "task_id": "0b7d4c92-3e51-4a86-9f2d-71c0a4e58b13",
  "version": 4,
  "published_at": "2026-09-02T11:20:41.006Z",
  "warnings": []
}
```

| | Draft | Published version |
| --- | --- | --- |
| Changed by | `PATCH /v1/tasks/{task_id}` | Publishing again, or rolling back |
| Used by | `task_version: "draft"` runs only | Runs by default, and every email batch row |
| Tool attachments | `tool_version: null` (latest published) | Pinned to the tool version live at publish |
| Numbering | — | `1`, `2`, `3`… never reused |

`GET /v1/tasks/{task_id}` returns the draft as `config`, the live version number as `published_version` (null if never published), its `published_at`, and the publish history as `versions` (each with `version`, `published_at`, `published_by`). The list endpoint leaves `versions` null.

## Saving and publishing

A draft can be unfinished: it saves without a prompt, an output field or a provider key. Checks come in two grades:

- **Every write** checks what a half-written task can still be judged on: duplicate output names, unpublished tools, unknown `{{tokens}}`, field shapes.
- **Publish** adds what needs a finished config: a provider key for the model, at least one `output` field, valid operation trees in attached tools, no attached tool containing a `handoff`, and no email batch that the change would break.

`POST /v1/tasks/{task_id}/validate` runs the publish checks without publishing and returns `errors` and `warnings`. `POST /v1/tasks/{task_id}/publish` refuses with a `400` listing every error, or freezes the draft as the next version and returns its warnings. Both are listed in [errors and limits](https://docs.talqing.com/tasks/errors-and-limits.md).

## What a publish freezes

- **Tools are pinned.** Each `tools` entry is stored as `{"tool_id": "…", "tool_version": 3}`. Republishing a tool does not reach the task until the task is published again.
- **MCP servers are not pinned.** The integration's `allowed_tools` is read live.
- **FAQ content is not pinned.** A version freezes which [FAQs](https://docs.talqing.com/agents/faqs.md) are attached; their entries are read at the start of each run.

`GET /v1/tasks/{task_id}/versions/{version}` returns one version's frozen config, tool pins included.

## Rollback

`POST /v1/tasks/{task_id}/versions/{version}/rollback` makes an earlier version live immediately.

- No new version is created; `published_version` points at the older one.
- The draft is replaced by that version's config. Unpublished edits are lost.
- The restored draft is unpinned, so publishing it again pins the tool versions current at that time.
- `updated_at` is set to that version's `published_at`, so the task does not read as having unpublished edits.
- The task's `name` reverts to that version's; a `409` if another task now uses it.
- A run already in flight finishes on the version it started with.

## Email batches follow the published version

A batch is refused against a task that has never been published (`publish '<name>' before drafting a batch with it`). After that, each drafting pass reads the task's **current** published version, so rows drafted after a publish use the new version; each row's run records which version drafted it.

A batch's CSV columns and `field_map` are fixed once rows are uploaded, so publishing or rolling back is refused while a drafting batch would break: a variable the new config makes required that the CSV has no column for, or an output field removed that `field_map` uses. `DELETE /v1/tasks/{task_id}` is refused with a `409` while a batch is still drafting with the task.

Deleting a task deletes its versions. Its runs survive with `task_id` null and `task_name` and `task_version` intact.

## Endpoints

| Endpoint | Does |
| --- | --- |
| `PATCH /v1/tasks/{task_id}` | Change the draft. Omitted fields are kept, `null` clears, objects merge, lists replace. |
| `POST /v1/tasks/{task_id}/validate` | Publish-grade checks, publishes nothing. |
| `POST /v1/tasks/{task_id}/publish` | Freeze the draft as the next version and make it live. |
| `GET /v1/tasks/{task_id}` | Draft, `published_version`, `versions`. |
| `GET /v1/tasks/{task_id}/versions/{version}` | One frozen config. |
| `POST /v1/tasks/{task_id}/versions/{version}/rollback` | Make that version live and replace the draft. |

In the dashboard, **History** in the task editor compares any two versions, including the draft.

## Next

- [Errors and limits](https://docs.talqing.com/tasks/errors-and-limits.md): Every refusal at save, publish and run time.
- [Running and runs](https://docs.talqing.com/tasks/running-and-runs.md): Which version a run uses, and `task_version` on the run.
