---
title: "Apply one patch to up to 50 tasks atomically."
method: POST
path: "/tasks/batch-update"
tags: ["tasks"]
---

# Apply one patch to up to 50 tasks atomically.

`POST /tasks/batch-update`

## Request

- Base URL: `https://restivity.ai/api/v1`
- URL: `https://restivity.ai/api/v1/tasks/batch-update`
- Auth: OAuth 2 (authorization code, authorization URL `https://restivity.ai/oauth/authorize`, token URL `https://restivity.ai/api/oauth/token`, scopes: `tasks:write`)

Same patch for every id (1–50). Admits exactly what `PATCH /tasks/{id}` admits: the tasks of the projects the caller may edit (a teammate's task included) and their own tasks with no project. Before any write, an id the caller cannot see is 404 `not_found` and a task they see but may not edit (a team viewer) is 403 `forbidden` — nothing written. Runs in one transaction: an id that escapes the rule in the database rolls back the whole batch (400 `invalid_request`, detail `batch_partial_failure`). The patch admits exactly the columns `PATCH /tasks/{id}` allows for bulk edits: title, description, status, priority, is_urgent, is_important, is_for_today, due_date, project_id (`""` clears due_date / project_id). The TEMPLATE (master) of a materialized recurring series is never written by a batch — for every field of the patch (AGF-382): it is left as it is and listed under `skipped` (`reason: "series_template"`, with a `hint`), while the other ids ARE written — never a refusal of the batch. Its occurrences are the tasks to complete, date or flag; the series changes through `PATCH /tasks/{id}/recurrence/rule` and moves with `PATCH /tasks/{id}` (`project_id`). MCP sibling: `tasks_batch_update`.

## Request body

- BatchUpdateBody
  - `ids` string[], required
  - `patch` BatchUpdatePatch, required
    - `title` string
    - `description` string: `description` takes HTML or marked text, at most 10,000 characters. HTML is kept as the task editor writes it (`<p>`, `<strong>`, `<em>`, `<ul>`/`<ol>` with `<li>`, `<code>`, `<a href="https://…">`) and sanitized by allow-list; marked text — blank-line paragraphs, `- ` and `1. ` lists, a `### ` heading opening a block, `**bold**`, `*italic*`, `` `code` ``, `[text](https://…)` links, code blocks fenced by three backticks — is rendered by the server into that HTML, so the board shows no asterisks or backticks. In one text the two do not mix: a text holding an HTML tag is stored as sent, markers included. A read returns the HTML in `description` and the same content without tags in `description_text`
    - `status` 'todo' | 'in_progress' | 'completed' | 'archived': Lifecycle state of a task.
    - `priority` 'low' | 'medium' | 'high' | 'urgent'
    - `is_urgent` boolean
    - `is_important` boolean
    - `is_for_today` boolean
    - `due_date` union
      - string, date-time
      - ''
    - `project_id` union
      - string, uuid
      - ''

## Response `200`

The updated tasks (`items`, in the order of `ids`) and the templates of materialized series the batch left out (`skipped`).

- BatchUpdateResult
  - `items` Task[], required: The updated tasks, in the order of `ids` (a skipped template is not among them).
    - `id` string, uuid, required
    - `user_id` string, uuid, required
    - `project_id` string, uuid, nullable, required
    - `title` string, required
    - `description` string, required: The stored HTML, as the task editor writes it (sanitized). To show it to a person read `description_text`.
    - `description_text` string, required: The description as plain text: every tag removed, one line per block, `- ` / `N. ` before list items, entities decoded — the same shape as a comment’s `content_text`. Show THIS to a person; `description` is the stored HTML. Read-only
    - `status` 'todo' | 'in_progress' | 'completed' | 'archived', required: Lifecycle state of a task.
    - `priority` 'low' | 'medium' | 'high' | 'urgent', required
    - `order` integer, nullable, required
    - `due_date` string, nullable, required
    - `is_for_today` boolean, required
    - `is_important` boolean, required
    - `is_urgent` boolean, required
    - `estimated_pomodoros` integer, required
    - `completed_pomodoros` integer, required
    - `is_recurring` boolean, required
    - `recurring_frequency` string, nullable, required
    - `recurring_end_date` string, nullable, required
    - `recurring_days` integer[], nullable, required
    - `parent_recurrence_id` string, uuid, nullable, required
    - `recurrence_model` 'materialized' | 'reminder_only', nullable, required
    - `recurrence_start_date` string, nullable, required: RES-115 — the series anchor ("Starts on") of a recurring master, a civil date at UTC midnight. The deadline is only a deadline: a `reminder_only` master's `due_date` is the occurrence it is currently on (it rolls forward when the occurrence is completed or skipped), a `materialized` master has none (each child occurrence carries its own). NULL on non-recurring rows and on child occurrences.
    - `completed_at` string, nullable, required
    - `assigned_to` string, uuid, nullable, required
    - `code` string, nullable, required
    - `created_at` string, required
    - `updated_at` string, required
    - `deleted_at` string, nullable, required
    - `url` string
    - `subtasks` Subtask[]
      - `id` string, uuid, required
      - `task_id` string, uuid, required
      - `title` string, required
      - `completed` boolean, required
      - `order` integer, nullable
      - `created_at` string
    - `tags` TagRef[]
      - `id` string, uuid, required
      - `user_id` string, uuid, required
      - `name` string, required
      - `color` string, required
      - `usage_count` integer, required
      - `deleted_at` string, nullable
    - `project` ProjectRef
      - `id` string, uuid, required
      - `name` string, required
      - `color` string, required
      - `prefix` string, nullable
      - `team_id` string, uuid, nullable
      - `team_name` string, nullable
      - `team_slug` string, nullable
    - `project_status` ProjectStatusRef
      - `id` string, uuid, required
      - `name` string, required
      - `color` string, required
      - `category` 'todo' | 'in_progress' | 'completed', required: Lifecycle category of a custom project status.
    - `assigned_to_profile` AssignedProfileRef
      - `id` string, uuid, required
      - `display_name` string, required
      - `photo_url` string, nullable, required
    - `planned_occurrences` PlannedOccurrence[]: AGF-381 — only on the master of a `materialized` series, from `tasks_get`: the next occurrences that do not exist yet (the nightly job creates each one 30 days before its day), up to the series end and at most 12, after the occurrences that already exist (those are real child tasks: `parent_recurrence_id` = this id). Absent on every other task and on list endpoints.
      - `date` string, required: The day of the occurrence (YYYY-MM-DD).
      - `creates_on` string, required: The day the nightly job creates it as a real task (YYYY-MM-DD): the date minus 30 days.
    - `series_change` SeriesChange
      - `change` 'to_reminder' | 'stop_repeating', required: `to_reminder`: the series is now a `reminder_only` master; `stop_repeating`: it is now a plain task.
      - `trashed_occurrence_ids` string[], required: The occurrences still to come (due today or later, not completed) — now in the Trash, restorable.
      - `kept_occurrence_ids` string[], required: The past and the completed occurrences — live, still linked to the series (`parent_recurrence_id`).
      - `reminder_due_date` string, nullable, required: `to_reminder`: the day the reminder is due (YYYY-MM-DD) — its next occurrence from today; null otherwise.
  - `skipped` BatchSkippedTask[], required: The ids the batch did NOT write, in the order of `ids`: the templates of materialized series (AGF-382). Empty when every id was written. Never a refusal of the batch: the other ids are written.
    - `id` string, uuid, required
    - `code` string, nullable, required: The task code (e.g. "RES-58"), null when it has none.
    - `reason` 'series_template', required: The template (master) of a materialized recurring series: a batch never writes it — every field of the patch is skipped on it.
    - `hint` string, required: What to do instead: its occurrences are the tasks to change; the series itself changes through `PATCH /tasks/{id}/recurrence/rule` (`tasks_recurrence_update_rule`), and moves with `PATCH /tasks/{id}` (`project_id`).

## Other responses

- `400`: Invalid request shape.
- `401`: Missing or invalid access token.
- `403`: Token lacks the required scope — or `forbidden`: the caller can see one of the tasks but may not edit it (only an editor of its project can), or a teammate's task would move where its owner cannot open it. Nothing written.
- `404`: One of the ids is not a task the caller can see. Nothing written.
- `409`: Transient, nothing written — send the same request again: `status_palette_busy` (the status list changed while the write waited) or `deadlock` (another change of the status list met this write).
- `422`: Request body failed schema validation.
- `429`: Rate-limit exceeded for this tier.

---

[API](https://skmtc.dev/restivity/apis/restivity-rest-api.md) · [All operations](https://skmtc.dev/restivity/apis/restivity-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/restivity/apis/restivity-rest-api/revisions/5764e41ebb3a?raw)
