# Changelog

> Changes to the Reassign API and to its documentation.

Source: https://reassign.dev/changelog/

This page lists the changes to the Reassign API and to its documentation.

## Project and priority on a plan request (main c86570e3)

2026-10-16

The API specification is generated from main commit `c86570e3c15fcc4a557f5be02dfd07ca3fe2fe74`, tag v0.2.35. This entry covers only the 10 main commits after develop `520e84f8`. On 2026-10-18, the live API specification is the same as this specification.

| Before                                                                                                                                                                                                                                                                                                              | Now                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A `POST /schedule/plan` request takes no project and no priority. A request with `projectId` or `priority` fails with `validation`. The booked event has `projectId: null` and `priority: 0`.                                                                                                                       | Exact and window requests take `projectId` and `priority`, as an event `create` does. The project must be live, else the row fails with `not_found` and the message `No live project with id "<id>".` With no `areaId`, the event takes the area of the project. `priority` is `0` to `4`, and an omitted value is `0`. `priority: null` fails with `validation`.                                                                                                                                                                                                                                       |
| A `POST /schedule/plan` retry with the same `requestId` that sends `areaId: null` or `activityTypeId: null` in place of an omitted key is a new request, and it books a second event.                                                                                                                               | `areaId: null` and `activityTypeId: null` match an omitted key. `projectId` and `priority` are content. `projectId: null` and `priority: 0` match an omitted key. A retry that adds a real id, or a priority from 1 to 4, in place of an omitted key is a new request.                                                                                                                                                                                                                                                                                                                                  |
| (Beta) On a day with a recurring event that crosses midnight, a `POST /command` change can go to the wrong one of the two occurrences. A new end, such as "wake me at 06:00", for the part from the day before fails with the notice `A change to "<name>" was skipped because its new end is not after its start.` | The server checks a new end or a new length against the start of the occurrence of the day. A new end with no new start that fits only the part from the day before changes that part. With `scope: "occurrence"`, only the occurrence of the day before changes. A change with no new end, such as a rename, still changes the occurrence of the day. For the occurrence of the day, the command drops a new end that is not after its start, with the notice `A new end was skipped because it is not after the start.` A new length for the occurrence of the day uses the start of that occurrence. |

The live MCP schemas of 2026-10-18 confirm the `projectId` and `priority` fields of `write_events` and the `loadByProject` field of `get_schedule`. The docs did not verify that the live MCP connector runs this commit. No MCP tool books a plan, so the MCP tools did not change. Reassign still has 10 MCP tools, and the public OpenAPI specification still has 26 operations. Refer to [Scheduling](https://reassign.dev/reference/scheduling.md) and [Quick command](https://reassign.dev/reference/command.md).

## Projects, priority, and command scope (develop 520e84f8)

2026-10-14

The API specification is generated from develop commit `520e84f82edccc35a52c8223a1128190686f246e`. Tag v0.2.29 (2026-10-14) holds this commit.

| Before                                                                                                                                          | Now                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| An event has no project and no priority.                                                                                                        | Each event has `projectId` (an id or `null`) and `priority` (`0` none, `1` low, `2` medium, `3` high, `4` urgent). This applies to `GET /events/{id}`, `GET /events/search`, `GET /schedule`, the `POST /events` and `POST /schedule/plan` results, and the `updated` rows of `POST /reviews/{date}`.                                                                                                                                |
| An Inbox item has no project and no priority.                                                                                                   | Each item has `projectId` and `priority`, in `GET /backlog`, in `GET /schedule` `backlog`, and in the `POST /backlog` results.                                                                                                                                                                                                                                                                                                       |
| `POST /events` `create` and `update` take no project.                                                                                           | They take `projectId` and `priority`. A project must be live, else the op fails with `not_found`. A new project with no `areaId` in the op gives the event the area of the project. On `update`, `projectId: null` clears the project and keeps the area. On an event from a task app, a new project that needs a move of the task fails with `read_only`. An omitted `priority` is `0` on `create` and keeps the value on `update`. |
| `POST /backlog` `capture`, `capture_text`, and `update` take no project.                                                                        | They take `projectId` and `priority`, with the same rules. The new project also replaces an area that the user set by hand. On an item that a task app owns, a new project can move the task to a list of that project. Else the op fails with `read_only`, and the message gives the reason.                                                                                                                                        |
| On `capture` and `capture_text`, an omitted `calendarId` uses the account default list.                                                         | It uses the default list of the project of the item first. Then it uses the account default list.                                                                                                                                                                                                                                                                                                                                    |
| `GET /categories` gives `areas` and `activityTypes`.                                                                                            | It also gives `projects`, and the list includes the archived projects. Each project has `id`, `name`, `areaId`, `order`, `archived`, and `publishCalendarId`.                                                                                                                                                                                                                                                                        |
| `POST /categories` takes `areas` and `activityTypes`.                                                                                           | It also takes `projects` ops: `create`, `update`, `delete`, `split`, and `relink`. Two live projects of a caller cannot have the same name, in any area (`conflict`). A delete of a project that a task list or a Linear project feeds fails with `conflict`. The response has `projects` rows only when the request sent project ops.                                                                                               |
| A delete of an area counts events, Inbox items, trackers, and calendar defaults.                                                                | It also counts the live projects of the area. A delete without `reassignTo` of an area with projects fails with `validation`, and `usage.projects` gives the count.                                                                                                                                                                                                                                                                  |
| `GET /schedule` gives the load by area and by activity type.                                                                                    | It also gives `projects` (an archived project has `archived: true`) and `loadByProject` for each day. A read of more than one day also gives a top-level `loadByProject`. `includeLookups=false` also leaves out `projects`.                                                                                                                                                                                                         |
| The Inbox filters of `GET /schedule` do not use the project or the priority.                                                                    | `backlogProjectId` (a project id, or `none`) and `backlogMinPriority` (0 to 4) filter the Inbox items. Both need `includeBacklog=true`.                                                                                                                                                                                                                                                                                              |
| `POST /command` gives `intents`, `questions`, `notices`, and the undo receipt.                                                                  | It also gives `created` (each new event and Inbox block), `written` (the indexes of the written `intents`), and `writtenLines`. The request takes `sourceUrl` and a `written` line on each `history` turn. A `create` or `park` intent can have `projectId` and `priority` (1 to 4).                                                                                                                                                 |
| **Breaking.** (Beta) A `delete` intent of `POST /command` has no `scope`. A delete on a recurring event removes one day only.                   | A `delete` intent takes `scope`: `occurrence`, `series`, or `future`. With no clear scope in the line, the command asks one `choice` question and does not delete. A changed occurrence, or an event from a calendar, gets no question.                                                                                                                                                                                              |
| A `series` or `future` command change from a changed occurrence does not change the series.                                                     | It applies to the series of that occurrence, and to the occurrence. When the new end is not after the start of the occurrence, the occurrence keeps its end, and a notice says so.                                                                                                                                                                                                                                                   |
| An undo checks the area and the activity type of an event or an Inbox item.                                                                     | It also checks the project and the priority. A hand change of them makes the token `stale`. `conflictingRows[].entity` can also be `projects` or `source_projects`. A project undo has four new `stale` messages. A token from before projects stays valid while the row has no project and the priority 0.                                                                                                                          |
| An `update` with `calendarId: null` and a `kind` change on an all-day calendar event fails with `read_only`.                                    | The op applies. The event is local after the op, so the kind lock of the calendar does not apply.                                                                                                                                                                                                                                                                                                                                    |
| AI classification of a new event can leave the area and the activity type empty. It starts about 5 seconds after the write.                     | For an event on the dial, AI always picks an area and an activity type for each empty field. A manual choice stays. Classification starts about 1 second after the write.                                                                                                                                                                                                                                                            |
| `GET /schedule` `integrations.defaultCalendarId` can name a writable calendar of a source that is not connected. A write refuses that calendar. | It is present only when the calendar is writable and its source is connected. `GET /calendars` uses the same rule.                                                                                                                                                                                                                                                                                                                   |

The MCP tools `get_schedule`, `write_events`, and `manage_inbox` take the same new fields, and `manage_categories` takes the same `projects` ops. The MCP server text now names projects and priority. Reassign still has 10 MCP tools, and the public OpenAPI specification still has 26 operations. The MCP claims come from a code comparison only. Refresh `tools/list`, and update code that parses events, Inbox items, or category lists with a strict schema. Refer to [Events](https://reassign.dev/reference/events.md), [Scheduling](https://reassign.dev/reference/scheduling.md), [Categories](https://reassign.dev/reference/categories.md), [Backlog](https://reassign.dev/reference/backlog.md), [Quick command](https://reassign.dev/reference/command.md), and [MCP server](https://reassign.dev/mcp.md).

## Plan copy styles and series copies (main 5b5115d9)

2026-10-09

The API specification is generated from main commit `5b5115d93af979d0cc9a747c9b43ca12444b01c2`, tag v0.2.18.

| Before                                                                                                                                                          | Now                                                                                                                                                                                                                                                                                                             |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Of the calendar fields, a `POST /schedule/plan` request takes `calendarId` and `mirrorCalendarIds` only. A request with `mirrorStyles` fails with `validation`. | Exact and window requests take `mirrorStyles`, as an event create does. Each key must be in the `mirrorCalendarIds` of the request, else the row fails with `validation`. The row `event` gives the styles, and the styles are part of the `requestId` check.                                                   |
| The `requestId` check uses a hash of the request content. The hash sorts only the top-level keys.                                                               | The hash sorts the keys at each level, and an array keeps its order. On a plan request and on a `create`, `mirrorStyles: {}` is the same as no map. A retry with `{}` in place of no map, or with the style keys in another order, matches. A retry with `mirrorCalendarIds` in another order is a new request. |
| An `update` that adds `mirrorCalendarIds` to a dial-only series fails with `validation`.                                                                        | A dial-only series with a valid calendar identity can have copies. A series that came from a connected calendar keeps this identity after `calendarId: null`. No read shows the identity. Other series still fail with the same message.                                                                        |
| A `scope: "future"` split of a dial-only series with copies fails with `validation`.                                                                            | The split applies. The new series starts with no copies and no `mirrorStyles`. The old series keeps its copies.                                                                                                                                                                                                 |
| The `Error` schema lists 11 codes.                                                                                                                              | It also lists `checkout_unlinked` (409) and an optional `unlinkedCheckout` field. Only the billing routes of the Reassign web app send them. A token or OAuth caller does not get them.                                                                                                                         |

The docs now use the new field labels of the Reassign web app: **Server address**, **New key**, and **Base URL**. Reassign still has 10 MCP tools, and the public OpenAPI specification still has 26 operations. Refer to [Events](https://reassign.dev/reference/events.md), [Scheduling](https://reassign.dev/reference/scheduling.md), [Errors](https://reassign.dev/errors.md), [Authentication](https://reassign.dev/authentication.md), and [MCP server](https://reassign.dev/mcp.md).

## Error details, category deletes, and copy styles (main 10a32db0)

2026-10-07

The API specification is generated from main commit `10a32db05027a14c67700b66a5417745b2764792`, tag v0.2.14.

| Before                                                                                                                    | Now                                                                                                                                                                                                                |
| ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Breaking.** A create or a rename to the name of a category that exists fails with `validation` (422).                   | It fails with `conflict` (409). The message gives the id of that category. This applies to `POST /categories` and MCP `manage_categories`.                                                                         |
| **Breaking.** A change of `plannedDate` or `plannedUntil` on an Inbox block that a task app owns fails with `permission`. | It fails with `read_only`. The message is the same.                                                                                                                                                                |
| **Breaking.** A `create` `id` in `POST /events` and MCP `write_events` can be any string.                                 | It must be a UUID. Else the op fails with `validation`: "id must be a UUID; omit it to let the server generate one."                                                                                               |
| The `error` object has only `code` and `message`.                                                                         | It can also have `issues`, `feature`, `conflictingRows`, `conflictingRowsTruncated`, `usage`, and `reassignTargets`. In a rejected batch, these fields are only in the rows. MCP tool errors have the same fields. |
| A 422 shows only the first bad field.                                                                                     | `error.issues` lists up to 20 bad fields, each with `path`, `message`, and an optional `expected` format. The message still names the first bad field. Bad op rows also have `issues`.                             |
| The plan refusal is `{ code, message }`.                                                                                  | REST adds `feature: "api"`. The MCP JSON-RPC 403 adds `data: { "code": "permission", "feature": "mcp" }`.                                                                                                          |
| You cannot delete a shared default category.                                                                              | The delete hides it, and your fork of it, for your account only. `deletedIds` can have two ids.                                                                                                                    |
| A delete of a category that events use needs `reassignTo`.                                                                | A delete of a category that events, Inbox items, trackers, or calendar defaults use needs `reassignTo`. `null` clears the links. A refused delete gives `usage` and `reassignTargets`.                             |
| An event write has no copy style.                                                                                         | `mirrorStyles` sets each copy to `full`, `private`, or `busy`. `GET /schedule` gives the default of each calendar as `copyStyle` and `busyCopyTitle`.                                                              |
| A series can have copies with no home calendar.                                                                           | A series with copies needs a home calendar, else the op fails with `validation`. A one-off event on the dial only can have copies.                                                                                 |
| `classification_pending` says that AI can change the area, the activity type, or the kind.                                | A `create` keeps its `kind`, and the `areaId` and `activityTypeId` that it sends. On `update`, `null` is a manual choice of no link, also in the Inbox.                                                            |
| Each write that changes data returns an `undoToken`. A `stale` undo row has only a message.                               | The receipt is optional, so a response with no `undoToken` does not prove a no-op. A `stale` row lists up to 50 `conflictingRows`. Undo stamps now cover the Inbox area and activity type.                         |
| Event search includes the events that start in the range.                                                                 | `GET /events/search` and MCP `find_event` include each event that overlaps a day of the range.                                                                                                                     |
| An empty `discard` of a day review returns empty lists.                                                                   | It also returns `noop: true`.                                                                                                                                                                                      |
| `nearestSlots` start on a 30-minute mark.                                                                                 | They start on a 15-minute mark, at a gap edge, or at the free minute nearest to the request. Window slots keep the 30-minute grid.                                                                                 |
| A reversed `POST /schedule/plan` window repeats the sent values in its message.                                           | The message is `` `latest` must be a local datetime after `earliest`. ``                                                                                                                                           |
| A fixed-start `POST /schedule/plan` request checks for conflicts as a blocking event.                                     | A fixed-start `non_blocking` or `reference` request does not fail with `conflict`.                                                                                                                                 |
| `GET /schedule` has no AI rule warnings.                                                                                  | `integrations.aiRuleWarnings` lists the areas and activity types in the AI rules that do not exist or are not unique.                                                                                              |
| `POST /command` shows the full span of an event from the day before.                                                      | The model sees only the part after midnight. More dropped items now get a notice.                                                                                                                                  |
| The docs do not say what an `update` on the id of a deleted occurrence does.                                              | It puts the occurrence back with the sent fields.                                                                                                                                                                  |
| The MCP server version is `0.0.1`.                                                                                        | It is `2026.10.3.1`, plus the build commit.                                                                                                                                                                        |

Inbox undo tokens from before this release can fail with `stale` until they expire, after 30 minutes or less. Reassign still has 10 MCP tools, and the public OpenAPI specification still has 26 operations. Refresh `tools/list`, and update code that reads the codes of these errors. Refer to [Errors](https://reassign.dev/errors.md), [Events](https://reassign.dev/reference/events.md), [Categories](https://reassign.dev/reference/categories.md), [Backlog](https://reassign.dev/reference/backlog.md), [Scheduling](https://reassign.dev/reference/scheduling.md), and [MCP server](https://reassign.dev/mcp.md).

## Agent feedback and write checks (main fa63b006)

2026-10-02

The API specification is generated from main commit `fa63b0068586c666680901eceea23fedd99f8493`, which includes PR #1330.

| Before                                                                                    | Now                                                                                                                                                                                                                                 |
| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| MCP `send_feedback` takes one top-level `kind` and `message`.                             | Send `reports`, an array of 1 to 10 `{ kind, message }` items, in one call per session. An optional top-level `submissionId` covers the whole submission. REST `POST /feedback` keeps its single-report body and HTTP 204 response. |
| Each feedback submission can send an acknowledgment email.                                | The user receives at most one acknowledgment per UTC day. Grouped MCP reports reach the team in one email.                                                                                                                          |
| MCP `write_events` always attempts the write.                                             | Optional `dryRun: true` checks the ops without applying them: valid ops are `skipped`, failures keep their usual errors, and there is no `undoToken`. REST `POST /events` does not accept this flag.                                |
| A successful event-write row gives no notice of queued AI classification.                 | It can include `warnings: ["classification_pending"]`: the saved event's area, activity type, or kind may change after the reply.                                                                                                   |
| Schedule reads return all free slots.                                                     | Optional `minDuration` (1 to 1440 minutes) filters `freeSlots` in MCP `get_schedule` and REST `GET /schedule`. Events and the full spans of qualifying gaps stay unchanged.                                                         |
| Event search sorts matches without returning their score.                                 | MCP `find_event` and REST `GET /events/search` include `score` from 0 to 1 on each event. Inbox matches have no score.                                                                                                              |
| A changed recurring occurrence omits `recurrence` and can omit inherited calendar fields. | It carries the series' recurrence and inherits calendar fields unless it chose its own calendar. Its stable id still uses the original date. `nextOccurrence` uses a moved occurrence's actual date.                                |
| A `scope: "future"` update can leave an override on the split date unchanged.             | The edit applies to that occurrence too. A time change replaces the override with the new series occurrence; other edits keep its own time. Later overrides keep their individual fields.                                           |
| `reflect` can mark an event on today or a future day.                                     | The occurrence must be on a past day in the account's time zone, as with `review_day` confirmation.                                                                                                                                 |
| Analytics can add a required `llm_model` argument to MCP tool schemas.                    | Model capture is off. The server strips stale `llm_model` and `conversation_id` arguments until 2026-12-01.                                                                                                                         |

Refresh `tools/list` and update saved feedback calls. Reassign still has 10 MCP tools, and the public OpenAPI specification still has 26 operations. Refer to [MCP server](https://reassign.dev/mcp.md), [Events](https://reassign.dev/reference/events.md), and [Scheduling](https://reassign.dev/reference/scheduling.md).

## Scheduling changes (main 27130c49)

2026-10-02

These changes are live on reassign.app. The API specification is generated from commit `27130c4985518827e3180511a76945b789988f76`, which includes PR #1329 and Chrome extension 1.3.2.

| Before                                                                   | Now                                                                                                                                                                                           |
| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| MCP has `schedule_events` and `confirm_schedule`.                        | Both tools are removed. Read `freeSlots` with `get_schedule`, choose a span, and create with `write_events`. For an existing Inbox item, keep using `manage_inbox` with `op: "schedule"`.     |
| `POST /schedule/plan` can return proposed `options` and a `commitToken`. | Every successful request books immediately and returns `event`. An exact `start` books there when free; a window books its top-ranked free slot. A window with no slot fails with `conflict`. |
| `POST /schedule/confirm` books a proposal.                               | The endpoint is removed. Read `GET /schedule` to choose a time before writing, then send the chosen span to `POST /events` or an exact-start request to `POST /schedule/plan`.                |
| A window can use `autoCommitBest: false` to request options.             | `autoCommitBest` is deprecated. A window accepts only `true` or an omitted field; both book immediately. `false` fails with `validation`, as does the field with an exact `start`.            |
| Window retry matching includes `autoCommitBest`.                         | Omitting it or sending `true` matches the same booked request. Keep the same `requestId` when retrying within 60 seconds. Older open proposals no longer replay.                              |
| Chrome extension capture sends `autoCommitBest: true`.                   | Extension 1.3.2 omits the field. Its capture retry keys remain compatible with 1.3.1.                                                                                                         |

Reassign now has 10 MCP tools. Refresh the tool list in your AI app and update saved calls. The optional analytics tool `get_more_tools` can appear as an eleventh tool. The public OpenAPI specification has 26 operations. Refer to [MCP server](https://reassign.dev/mcp.md#schedule-new-work) and [Scheduling](https://reassign.dev/reference/scheduling.md) for the current flows.

## MCP changes (main 59d36f40)

2026-10-02

PRs #1327 and #1328 reduce the Reassign MCP tool set from 14 to 12 tools. Refresh the tool list in your AI app and update any saved tool calls. The old tool names are no longer registered.

| Before                                                           | Now                                                                                                                                                                                                                                                                      |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `schedule`                                                       | `schedule_events`, with the same arguments and results.                                                                                                                                                                                                                  |
| `undo`                                                           | `undo_changes`, with the same arguments and results.                                                                                                                                                                                                                     |
| `manage_backlog`                                                 | `manage_inbox`. Its operations, including `schedule`, keep their names.                                                                                                                                                                                                  |
| `get_weather` and `get_energy`                                   | Use `get_schedule` with `includeWeather: true` or `includeEnergy: true`. Set `from` and `to` to the same date. `weatherLocation` replaces the weather tool's `location` argument and requires `includeWeather: true`. The reports are strings in `weather` and `energy`. |
| `find_event` searches scheduled events only.                     | It also returns `inbox`, `inboxMatchedCount`, and `nextInboxOffset`. Send `inboxOffset` to page through up to 50 Inbox matches at a time. Date and time-of-day filters apply to events only; `ambiguous` also counts events only.                                        |
| MCP `get_schedule` takes `backlogQuery`.                         | Use `find_event` to find an Inbox item by name. `includeBacklog`, `backlogPlannedOn`, and `backlogOffset` still work in `get_schedule`.                                                                                                                                  |
| A weather report for another place does not label its time zone. | The report names the IANA time zone used for its date and times. This also applies to REST `GET /weather` with `location`.                                                                                                                                               |
| A failed scheduling confirmation says `Re-run schedule`.         | It says `Re-run schedule_events`, also in REST `POST /schedule/confirm` error messages.                                                                                                                                                                                  |

REST keeps its existing route names, `GET /schedule` `backlogQuery`, and separate weather and energy endpoints. `GET /events/search` continues to return events only. The optional analytics tool `get_more_tools` can appear as a thirteenth tool. See [MCP server](https://reassign.dev/mcp.md) for the current tools and examples.

## API changes (main 7d09cfa2, v0.1.80)

2026-10-02

These changes are in the app release v0.1.80.

| Before                                                                                                                                        | Now                                                                                                                                                                                                                                                        |
| --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /schedule/plan` and `POST /schedule/confirm` (MCP `schedule` and `confirm_schedule`) are not atomic. Each item succeeds or fails alone. | They are atomic by default. When one row fails, the server writes nothing, and the other rows are `skipped`. Send `partial: true` for the old behavior. A replayed row stays `ok`. Refer to [Scheduling](https://reassign.dev/reference/scheduling.md).    |
| A taken exact `start` in a plan request returns `options` and a `commitToken`.                                                                | It fails with `conflict`, `conflicts`, and up to 5 `nearestSlots`. To take a slot, send its `start` as a new request.                                                                                                                                      |
| A plan request has no calendar fields.                                                                                                        | It takes `calendarId` and `mirrorCalendarIds`, as an event create does.                                                                                                                                                                                    |
| A confirm of a token that you already confirmed gives `This token is not awaiting commit (status: active).`                                   | It gives `This token is already committed. Use its undoToken to reverse it.` A token two times in one call gives `Duplicate token in this call.`                                                                                                           |
| An undo row result has a shape for each kind of write. A command, category, or Inbox undo gives `reversed`.                                   | Every undo row is `{ reverted, restored, removed, voided, occurrenceRestored? }`.                                                                                                                                                                          |
| An undo reverts a row also when the row changed after the write.                                                                              | The undo fails with the new code `stale` (409) and changes nothing. A token from before this release is also `stale`. Refer to [Stale tokens](https://reassign.dev/reference/scheduling.md#stale-tokens).                                                  |
| An occurrence gives its series only in the part of its id before `@`.                                                                         | Each occurrence has `seriesId` and `originalDate` in all reads and write echoes.                                                                                                                                                                           |
| `GET /schedule` always gives `areas`, `activityTypes`, and `integrations`.                                                                    | `includeLookups=false` leaves them out. A `series` row also has `kind`, `source`, `calendarId`, `mirrorCalendarIds`, and `readOnly`. It has no `notes` now.                                                                                                |
| `GET /events/search` (MCP `find_event`) gives the occurrence of a series that is nearest to today.                                            | It gives the occurrence that matches best. On a tie, it gives the one nearest to today.                                                                                                                                                                    |
| `POST /backlog` `capture` and `capture_text` make a block in Reassign only.                                                                   | They take `calendarId`, the Source of the new block. A task list Source also creates the task in that list. With no `calendarId`, the block takes the default Source. Refer to [Backlog](https://reassign.dev/reference/backlog.md#source-of-a-new-block). |
| A `park` loses the planned day and the ticked steps.                                                                                          | The Inbox block keeps the day of the event as `plannedDate`, and it keeps the ticked steps.                                                                                                                                                                |
| `POST /reviews/{date}` `confirm` gives no warning on a day with no marks. `discard` on a day with nothing recorded returns 404.               | `confirm` adds a `warning`. `discard` returns 200 with empty lists and no `undoToken`.                                                                                                                                                                     |
| `adherence.event` and `adherence.layer` are 1 on a day with no planned time.                                                                  | They are `null`. Refer to [Day reviews](https://reassign.dev/reference/day-reviews.md).                                                                                                                                                                    |
| The energy and weather reports, and the MCP prose, use 12-hour times (`3pm`).                                                                 | They use 24-hour times (`15:00`).                                                                                                                                                                                                                          |
| An MCP tool refusal is a text message. A tool result can have a second `{"conversation_id"}` block.                                           | A refusal is the JSON error envelope, also for a prose tool. A result has no `conversation_id` block. Refer to [MCP server](https://reassign.dev/mcp.md).                                                                                                  |

## API changes (code-cleanup 7b08651c)

2026-09-29

These changes reach reassign.app with the next app release.

| Before                                                                                                             | Now                                                                                                                                                                                                                                                                                      |
| ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /command` gives the answer to a question in `notices`.                                                       | It gives the answer in `reply`, a maximum of 400 characters. A request that is not about the plan gets `I can only help with your plan.` Refer to [Quick command](https://reassign.dev/reference/command.md).                                                                            |
| `POST /command` intents are `create`, `update`, `shift`, `delete`, `clear`, `park`, `log_moment`, and `checklist`. | New intents: `checklist_edit`, `split`, `remember`, `reflect`, and `day_note`. `update` can also change `end`, `name`, `kind`, `areaId`, `activityTypeId`, and the notes. `update`, `checklist`, and `checklist_edit` have `scope` for a recurring event. `create` can have `checklist`. |
| `POST /command` always plans changes.                                                                              | A `line` command about a past day, or about how today went, runs in review mode. It records marks and day notes, and it does not change the plan.                                                                                                                                        |
| `POST /command`: a `clear` skips calendar-synced events with no notice.                                            | It adds the calendar-synced notice for each event that it skips.                                                                                                                                                                                                                         |
| The limit of 600 requests per minute counts each Bearer token. An OAuth token that is not valid can get 429.       | It counts each OAuth grant (all its access tokens together) or each personal access token. An OAuth token that is not valid gets 401 first. Refer to [Errors](https://reassign.dev/errors.md#rate-limits).                                                                               |
| A refresh token from before rotation works one time.                                                               | It fails with 400 `invalid_grant`. A failed grant read also gives 503 `temporarily_unavailable`.                                                                                                                                                                                         |
| `get_schedule` and `GET /schedule` give `integrations.sources[].account` only to first-party callers.              | Every caller gets `account`. `GET /calendars` still gives `account` only to first-party callers.                                                                                                                                                                                         |
| `GET /events/search` with only a `to` more than 7 days in the past returns 422.                                    | The default start moves, so that the window is valid.                                                                                                                                                                                                                                    |
| A `reflect` op accepts an actual span of any length.                                                               | The span must be from 5 minutes to 168 hours.                                                                                                                                                                                                                                            |
| A batch can have more than one `scope: "future"` op for a series.                                                  | A batch can have one `scope: "future"` op for each series, and no op on the bare series id with it.                                                                                                                                                                                      |
| The `clear` result has no count of the read-only events that it kept.                                              | It gives `skippedReadOnly`.                                                                                                                                                                                                                                                              |
| `POST /backlog`: only Google Tasks, Microsoft To Do, and Linear accept a date change, and only for one day.        | Todoist and TickTick also accept it, and a `plannedUntil` is allowed. A task that recurs in the task app still fails with `permission`.                                                                                                                                                  |
| An `areaId` or `activityTypeId` of a shared default that you forked links to the shared default.                   | It links to your fork.                                                                                                                                                                                                                                                                   |

## API changes (develop 9d9856b6)

2026-09-24

These changes reach reassign.app with the next app release.

| Before                                                                                                                                                                     | Now                                                                                                                                                                                                                                                                 |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A change to one occurrence gives it a new id, and the event has `derivedFromId` (the series id).                                                                           | The occurrence keeps the id `seriesId@<original date>`, also after a move to another day. `derivedFromId` is removed. The series id is the part before `@`. An id from an old write result still works. Refer to [Core concepts](https://reassign.dev/concepts.md). |
| `GET /events/{id}` with the occurrence id of a moved occurrence can return 404.                                                                                            | It returns the moved occurrence.                                                                                                                                                                                                                                    |
| `POST /schedule/plan` (MCP `schedule`) books a window request when only one slot fits.                                                                                     | A window request books only with `autoCommitBest: true`. Otherwise it returns `options` and a `commitToken`, also for one slot. A free exact `start` still books.                                                                                                   |
| A plan request with the same content in 60 seconds replays the first result, also with no `requestId`.                                                                     | Only a `requestId` replays (60 seconds, same content). A request without `requestId` is always new.                                                                                                                                                                 |
| `calendarId: null` on `update` removes only the home link, and it cannot carry other fields.                                                                               | It also deletes all mirror copies. It can carry `start`, `end`, `name`, `notes`, `areaId`, `activityTypeId`, and `kind`. It works on a read-only event. It still refuses `mirrorCalendarIds`, `recurrence`, `recurrenceEnd`, `sourceUrl`, and `focusIntervals`.     |
| An `update` with `mirrorCalendarIds` checks every id. Thus, a kept copy on a read-only or disconnected calendar fails.                                                     | The API checks only the added ids. A kept id can stay.                                                                                                                                                                                                              |
| The API has no limit for each token.                                                                                                                                       | 600 requests per minute for each Bearer token: 429 `rate_limited`, "Too many requests.", `Retry-After: 60`. The MCP server has no such limit. Refer to [Errors](https://reassign.dev/errors.md#rate-limits).                                                        |
| `GET /schedule` gives `integrations.sources[].account` to every caller.                                                                                                    | Only a personal access token and the Reassign apps get `account`. A third-party OAuth app and the MCP tool `get_schedule` do not.                                                                                                                                   |
| A bad-op row has the message `Invalid "create" op. name: …`. An op that is not an object gets `Unknown op undefined. …`.                                                   | The message is ``Invalid "create" op. `name`: …``. An op that is not an object gets "Each op must be an object." This applies to `POST /events`, `POST /backlog`, `POST /categories`, and the MCP write tools.                                                      |
| `POST /command`: an unknown area or activity type on a new event or Inbox item skips the item.                                                                             | The item stays without that label, with a notice. A name in an id field that matches one row resolves to its id. An unknown calendar still skips the event.                                                                                                         |
| `POST /command`: a new `park` item keeps any positive `durationMinutes`.                                                                                                   | A value that is not from 5 to 1440 drops the item, with a notice.                                                                                                                                                                                                   |
| The MCP `tools/list` has 14 tools.                                                                                                                                         | It can also have `get_more_tools`. This tool records a missing-tool report and changes no data. Refer to [MCP server](https://reassign.dev/mcp.md).                                                                                                                 |
| An OAuth refresh replay in the 60-second window gets the scope of the token that you sent. Thus, a replay of an old token can widen a family that a refresh made narrower. | It gets the scope of the live token, cut to the scope of the token that you sent. It cannot widen the scope.                                                                                                                                                        |

## API fixes (PR #1071)

2026-09-24

These fixes reach reassign.app with the next app release.

| Before                                                                                                               | Now                                                                                                                                                                           |
| -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /events/batch`, `PATCH /events/{id}`, `DELETE /events/{id}`, and `GET /reviews/{date}` get HTTP status 405     | 401 `unauthorized` without a valid token, else 404 `not_found`. The Reassign Chrome extension 1.1.x gets 422 `validation`. Refer to [Errors](https://reassign.dev/errors.md). |
| A token with only `events:read` and a bad body on a write route gets 422 `validation`                                | 403 `scope`. A write route checks the scope before the body.                                                                                                                  |
| A bad op in `POST /backlog` or `POST /categories` fails the full call with 422                                       | The bad op is an error row with the code `validation`.                                                                                                                        |
| An atomic `POST /categories` call that fails gives an error row for each op, with the message "Batch rolled back: …" | Only the failed op is an error row. The valid ops are `skipped` rows with a `reason`.                                                                                         |
| The backlog `schedule` op does not check the span or conflicts                                                       | The span is 5 minutes to 168 hours. An overlap with a blocking event fails with `conflict`, `conflicts`, and `nearestSlots`.                                                  |
| Backlog `durationMinutes` is 1 to 1440                                                                               | Backlog `durationMinutes` is 5 to 1440                                                                                                                                        |
| A `POST /events` or `POST /backlog` call can get 503 after its writes changed data                                   | HTTP status 200 with the `undoToken`. A row that the server cannot read again has the code `internal`.                                                                        |
| A tracker has `isGlobal` and `overridesId` only when set                                                             | `isGlobal` is always present. `overridesId` is always present, `null` when not set.                                                                                           |
| The `order` of an area or an activity type can be `null`                                                             | `order` is always an integer                                                                                                                                                  |
| A `create` or `park` intent of `POST /command` has `areaId` and `activityTypeId` only when set                       | Both are always present, `null` when not set                                                                                                                                  |
| A `log_moment` intent of `POST /command` changes a rating that is out of bounds to the nearest bound                 | The command drops the log with a notice, for example "A log was skipped because its value 7 is not from 1 to 5." A rating that is not a whole number is also dropped.         |
| `PATCH /me` works with a token that has `events:write`                                                               | `PATCH /me` serves the Reassign web app only. A token gets 403 `permission`.                                                                                                  |
| The error code `ambiguous` is in the list of codes. No route sends it.                                               | Removed                                                                                                                                                                       |

## API v1 contract change

2026-09-23

> This is a breaking change. The base URL stays `https://reassign.app/api/v1`.
> This change reaches reassign.app with the next app release. Until then,
> reassign.app can answer with the old shapes.

The REST API and the MCP server now use one contract: one name and one format for each value. The docs show the new contract. Refer to [Core concepts](https://reassign.dev/concepts.md).

### General rules

- A request with an unknown key or an unknown query parameter now gets HTTP status 422. Before, the API ignored it.
- A body that is not valid JSON now gets HTTP status 422. Before, the API read it as `{}`.
- Enum values are lowercase snake\_case, for example `non_blocking`, `hatch_r`, `google_tasks`, and `log_moment`.
- A time stamp is an ISO 8601 instant in UTC, for example `"2026-09-23T07:00:00.000Z"`. Before, some fields used milliseconds since the epoch.
- A response with a span gives `timezone` one time, at the top level.
- A batch write that changes data returns `undoToken` and `expiresAt` at the top level. A write to `/moments` or `/trackers` returns no `undoToken`.
- A create on `/moments` and `/trackers` now answers with HTTP status 201.

### What changed

| Before                                                                                                                                                  | Now                                                                                                                                         |
| ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| A span as `date` and `"HH:MM"` times                                                                                                                    | `start` and `end` as local datetimes `"YYYY-MM-DDTHH:MM"`                                                                                   |
| `endNextDay`, `crossesMidnight`, `endsAtDayBoundary`, `continuesFromPrevDay`                                                                            | Removed. A span past midnight has its `end` on the next date. Midnight is `"<next date>T00:00"`.                                            |
| `startHour`, `endHour`, `durationMinutes`, `label`, and `isRecurringInstance` on an event, and `startIso` and `endIso` on search and scheduling results | Removed. Use `start` and `end`.                                                                                                             |
| An event is 5 minutes to 24 hours long, on a 5-minute grid                                                                                              | An API write is 5 minutes to 168 hours long. The server keeps the exact minute.                                                             |
| `syncTo`                                                                                                                                                | `calendarId`                                                                                                                                |
| `mirrorTo`                                                                                                                                              | `mirrorCalendarIds`                                                                                                                         |
| `areaName`, `activityTypeName` in a write                                                                                                               | Removed. Send `areaId` and `activityTypeId` only.                                                                                           |
| `area` and `activityType` objects on an event                                                                                                           | `areaId` and `activityTypeId`, always present, `null` when not set. `GET /schedule` gives the `areas` and `activityTypes` lists.            |
| `occurrenceDate`                                                                                                                                        | The occurrence id `seriesId@YYYY-MM-DD`                                                                                                     |
| `scope`: `"this"`, `"future"`, or `"all"`                                                                                                               | `scope`: `"future"` only, with an occurrence id. An occurrence id alone changes that occurrence. A series id alone changes the full series. |
| `recurrence` presets (`daily`, `weekly`, …) and `"none"`                                                                                                | An RRULE only. `null` stops the series.                                                                                                     |
| `kind: "non-blocking"`                                                                                                                                  | `kind: "non_blocking"`                                                                                                                      |
| `POST /events` with one event, `POST /events/batch`, `PATCH /events/{id}`, and `DELETE /events/{id}`                                                    | `POST /events` with `{ ops, partial? }`, the one event write endpoint                                                                       |
| The `move` op                                                                                                                                           | The `update` op with a new `start` or `end`                                                                                                 |
| The `clear` op with `date` and `to`                                                                                                                     | The `clear` op with `from` and `to` (both required)                                                                                         |
| `GET /events?date=` (the events of one day)                                                                                                             | `GET /schedule?from=&to=`                                                                                                                   |
| `GET /events?updatedSince=` and a numeric `cursor`                                                                                                      | `GET /events?cursor=`, with an opaque `cursor` string                                                                                       |
| `GET /reviews` and `GET /reviews/{date}`                                                                                                                | Removed. `GET /schedule` gives a `review` block on each confirmed day.                                                                      |
| `applied`, `failed`, and `skipped` counts in a batch result                                                                                             | Removed. Read the `status` of each row in `results`.                                                                                        |
| A row `error` string and `errorCode`                                                                                                                    | A row `error` object: `{ code, message, conflicts?, nearestSlots? }`                                                                        |
| A rejected batch body without the `error` object                                                                                                        | `{ error, timezone?, results }`. When the failed ops have different codes, the status is 400 and the code is `batch_rejected`.              |
| `batchUndoToken`, and an `undoToken` in each row                                                                                                        | One `undoToken` and `expiresAt` at the top level                                                                                            |
| `expiresInMs`, and `expiresAt` in milliseconds                                                                                                          | `expiresAt` (an instant)                                                                                                                    |
| `suggestedFollowup` text in an MCP result, and the `concise` argument of `write_events`                                                                 | Removed                                                                                                                                     |
| `request_id` in a scheduling request                                                                                                                    | `requestId`                                                                                                                                 |
| `duration` (for example `"90m"`) and `date` in a scheduling request                                                                                     | `durationMinutes` (a whole number), and `start` or `earliest` and `latest` as local datetimes                                               |
| `GET /schedule` with `date`, `dates`, or `compact`                                                                                                      | `GET /schedule` with `from` and `to` (both required)                                                                                        |
| `now` as an object in `GET /schedule`                                                                                                                   | `now` as a local datetime, and `timezone` at the top level                                                                                  |
| `userId` in `GET /me`                                                                                                                                   | `id`                                                                                                                                        |
| `trialEndsAt` in milliseconds, only on a trial                                                                                                          | `trialEndsAt` as an instant, or `null`                                                                                                      |
| `email` in `GET /me` only when present                                                                                                                  | `email`, always present, `null` when there is no address                                                                                    |
| `focusIntervals` with `plannedIntervals` and `completedIntervals`                                                                                       | `focusIntervals: { focusMin, breakMin }` and `completedFocusIntervals`                                                                      |
| `checklist: { items, done, total }` on an event                                                                                                         | `checklist: { items }`                                                                                                                      |
| `reflect: { state, actualStartHour, actualEndHour, … }`                                                                                                 | `reflect: { status, actualStart?, actualEnd? }`                                                                                             |
| `durationHours` and `steps` in the Inbox                                                                                                                | `durationMinutes` and `checklist: { items }`                                                                                                |
| `eventId` in the `park` op                                                                                                                              | `id`                                                                                                                                        |
| `date`, `hour`, and `trackerName` on a moment                                                                                                           | `time` (a local datetime)                                                                                                                   |
| `GET /moments?date=` and `DELETE /moments?date=`                                                                                                        | `GET /moments` and `DELETE /moments` with `from` and `to` (both required)                                                                   |
| `deleted`, `reassignedTo` in a category delete result                                                                                                   | `deletedIds`                                                                                                                                |
| `isDefault` on a calendar                                                                                                                               | Removed. Compare the `id` with `defaultCalendarId`.                                                                                         |
| `notes: ""` on an event with no notes                                                                                                                   | `notes` is not there                                                                                                                        |

## API v1 documentation published

2026-09-23

- The docs cover the REST API v1 at `https://reassign.app/api/v1`: [authentication](https://reassign.dev/authentication.md), [errors](https://reassign.dev/errors.md), and a reference page for each group of calls, for example [Events](https://reassign.dev/reference/events.md) and [Scheduling](https://reassign.dev/reference/scheduling.md).
- The docs show how to [connect an AI assistant](https://reassign.dev/mcp.md) to the Reassign MCP server.
- The [Quickstart](https://reassign.dev/quickstart.md) and [Core concepts](https://reassign.dev/concepts.md) pages explain the first requests and the model of a day plan.
