# Create or resolve tasks — one, or several linked to each other

`POST /api/v1/tasks/batch`

MCP tool: `upsert-tasks`

The only way to create a task: 1 to 100 items, all written or none. `project`, `status` and `title` are the whole of a capture. The answer is one acknowledgement per item, in order, beside its `ref`; `get-task` reads the whole task.
**A `dedupe_on` hit resolves, it never rewrites**: the task stays as it was (`resolved: true`, `ignored` naming what it did not write); `references`, `links` and `wait` still attach. Change its fields with `update-tasks`.
Refused, rolling back every item: a `calendar` item with no date (`400`), a `dedupe_on` hit moving a closed task (`409`), a duplicate `ref` or a link to a `ref` no item carries (`400`). The rest lands and is reported: a `blocks` cycle is stored, a task that `blocks` itself is flagged, a `{ "title" }` end comes back under `unresolved`.

## Responses

- `200` — Every item was written or resolved; one acknowledgement each, in the order sent
- `400` — The request body or query string does not match the operation's schema, or a `calendar` task names no date (`calendar_requires_date`)
- `404` — No such task
- `409` — The status change is not one the GTD state table allows. The body's `error.code` is `invalid_transition` and `error.meta` carries `from`, `to` and `allowed` — the statuses this task can actually move to.
