# API changes: September 12, 2026

[← Changelog](/docs/changelog) · [September 2026](/docs/changelog/2026-09)

### NEW-0912-2: device_id parameter in the device-flow confirmation link

`POST /v1/connect/device/authorize` appends `device_id` to the `verification_uri_complete`
link when the value sent in the request body fits the identifier shape: up to 128 characters,
Latin letters, digits and the characters `. : - _` only. The value goes into the link as it
arrived, but under URL rules, so a `:` appears as `%3A` in the address — ordinary query-string
parsing decodes it back.

A value outside that shape is dropped silently: the endpoint still answers `200`, the link
arrives without the parameter, and the response carries no sign of refusal. This is deliberate —
the field is optional, and refusing would break sign-in for anyone whose identifier drifted.

This exists for login statistics: people often open the confirmation link in a different
browser from the one where they started — copying it, or handing it to the system browser —
and without a parameter inside the link itself there is nothing to tie the steps of one login
together.

The change is additive: a request without `device_id` gets the link in its previous form, the
other response fields are unchanged, and no client action is required.

### BC-0912-3: a meaningless body on message edit, chat members and post update is refused, not executed

> Old format supported until: not provided

**Before**

Three messenger and feed routes accepted a body without the required data and answered with success while doing something other than what the client asked. [PATCH /v1/chats/{dialogId}/messages/{messageId}](/docs/chats/messages/update) without `message` — empty, whitespace-only, `[BR]` line-break tags only, or with the text under another field name (`{"text": …}`) — sent an empty text to Bitrix24 and answered `200` "Message updated", while Bitrix24 **deletes the message** on a text that is empty by its own rule in this method (verified 2026-09-12: after such a call the message shows as deleted). The whole request body travelled to Bitrix24 too, and Bitrix24 folds parameter names case-insensitively taking the last key: `{"MESSAGE": "text", "Message": ""}` deleted the message as well (verified 2026-09-12). [DELETE /v1/chats/{chatId}/users](/docs/chats/members/remove) and [POST /v1/chats/{chatId}/users](/docs/chats/members/add) forwarded `userId` and the elements of `users` to Bitrix24 as given, and Bitrix24 casts them to a number by PHP rules: the array `[47]` becomes `1`, a word becomes `0`. The call `{"userId": [47]}` answered `200` and removed the user with ID 1 — the account owner — from the chat instead of 47; on addition `{"users": ["abc"]}` answered `200` "added" while adding nobody, and a nested array crashed Bitrix24 (verified 2026-09-12). [PATCH /v1/posts/{id}](/docs/feed/posts/update) without a single changeable field (`{}` or the text under the name `message`) answered `200` "Post updated" after two calls to Bitrix24 that changed nothing.

**After**

Message edit requires a `message` with text by Bitrix24's own rule: empty, blanks only, `[BR]`/`[br]` tags only, neither a string nor a number, under another field name — `400 MESSAGE_REQUIRED` before Bitrix24 is called, with the unrecognized field named in the error text; a finite number is accepted and stored as text. Deleting a message is only via [DELETE /v1/chats/{dialogId}/messages/{messageId}](/docs/chats/messages/delete). `userId` on member removal and every element of `users` on member addition must be a positive integer — a number, or a string of decimal digits without leading zeros or spaces, up to 2^53−1; otherwise `400 INVALID_PARAMS` (for `users` the element index and type are named), an absent `userId` or an empty `users` — `400 MISSING_PARAMS`. The value reaches Bitrix24 as a normalised number. String forms Bitrix24 resolved itself and that passed through the former forwarding (`department<id>`, `structure<id>`, `network<id>`) are now refused — only user ids are accepted. On message edit and member removal only the checked parameters travel to Bitrix24 (`MESSAGE`; `CHAT_ID` and `USER_ID`): other body keys, including native upper-case Bitrix24 keys (`URL_PREVIEW`, `IS_EDITED`, `ATTACH`, `KEYBOARD`) that used to ride along with the body, are no longer forwarded. A post update without any of `text`, `title`, `recipients`, `files` — `400 MISSING_PARAMS` before Bitrix24 is called, with the unrecognized field named. A message edit call without a request body, which answered `500`, now gets the same `400 MESSAGE_REQUIRED`.

**What integrators should do**

Make sure a message edit always carries the text in the `message` field, and chat members are passed as numbers (or strings of digits without leading zeros) — `userId` as a scalar, `users` as a flat array. A post update must carry at least one changeable field. If the body of a message edit or a member removal carried native Bitrix24 keys beyond the documented fields — they no longer take effect. Calls that used to succeed with these bodies were actually deleting the message, touching the wrong member or changing nothing — now they get a refusal with an explanation.

**Affected endpoints:** [PATCH /v1/chats/{dialogId}/messages/{messageId}](/docs/chats/messages/update), [POST /v1/chats/{chatId}/users](/docs/chats/members/add), [DELETE /v1/chats/{chatId}/users](/docs/chats/members/remove), [PATCH /v1/posts/{id}](/docs/feed/posts/update).

### BC-0912-4: numeric path ids of the chat and notification endpoints are checked before the Bitrix24 call

> Old format supported until: not provided

**Before**

The path id was read leniently: a value with a trailing tail or a fraction (`125abc`, `125.9`) reached Bitrix24 as 125 and the operation ran against record 125 — the response was `200` or `204`, as for a correct call. A fully non-numeric value (`abc`) reached Bitrix24 empty and was refused there (`403` or `422`).

**After**

The id must be a positive integer in canonical form: no sign, leading zero, fraction, exponent or spaces. Otherwise the endpoint answers `400` before the Bitrix24 call: for `chatId` — `INVALID_CHAT_ID` (as [PATCH /v1/chats/:chatId](/docs/chats/management/rename) already answered), for `messageId`, `fileId` and a notification `id` — `INVALID_PARAMS`. The error text names the parameter. Well-formed ids are processed as before.

**Affected endpoints:** [DELETE /v1/notifications/:id](/docs/notifications/delete), [GET /v1/chats/files/:fileId](/docs/chats/files/file-get), [PATCH /v1/chats/:dialogId/messages/:messageId](/docs/chats/messages/update), [DELETE /v1/chats/:dialogId/messages/:messageId](/docs/chats/messages/delete), [POST /v1/chats/:chatId/leave](/docs/chats/management/leave), [POST /v1/chats/:chatId/owner](/docs/chats/management/owner), [POST /v1/chats/:chatId/users](/docs/chats/members/add), [DELETE /v1/chats/:chatId/users](/docs/chats/members/remove), [POST /v1/chats/:chatId/files](/docs/chats/files/upload), [GET /v1/chats/:chatId/folder](/docs/chats/files/folder).

**What integrators should do**

Pass the id exactly as the API returned it: digits only. If the id is assembled from a string, strip tails and separators before putting it into the path. Clients that already pass ids as numbers change nothing.

### FIX-0912-5: a pre-existing NodeSource configuration now survives an interrupted runtime install

**Before**

When deploying a `node20*` runtime onto a reused or hand-configured virtual machine, Vibecode moved
your `/etc/apt/sources.list.d/nodesource.list` and `nodesource.sources` off the live path for the
duration of the install and put them back at the end. If the install step was interrupted — by a
timeout or by a dropped connection — the files stayed deleted on the server: later Node.js installs
and upgrades over apt stopped using your repository until you restored it by hand.

**After**

An interrupted install step no longer loses your configuration: it stays on the server, and the
next runtime deploy puts the files back on the live path on its own. When the NodeSource install
succeeds, the live path keeps your file and the installer-created one is removed. Those two files
are what gets restored: `/etc/apt/preferences.d/nodejs`, `nsolid` and the
`/usr/share/keyrings/nodesource.gpg` key are overwritten by the installer and the platform does not
put them back. If your `nodesource.sources` was created by the ordinary NodeSource install command,
it matches the installer's file content for content, the platform cannot tell them apart, and an
interrupted install may delete such a file — the same command restores it. 

One special case: if your own `nodesource.list` sits next to it and **names the same NodeSource
repository and the same release (`nodistro`) on an active line**, the platform moves its own file off
the live path **on an ordinary deploy, with no interruption at all**. The file is not deleted — it
stays beside it under a name suffixed `.vibe-node20-conflict.disabled`, apt does not read it, and you
can remove it by hand. If an identical file from an earlier such fix is already there, the repeat one
is simply removed from the live path. What this is for: if your line names that repository with a different key, apt
refuses to read the source list as a whole and no packages can be installed on the server any more.
If the key is the same there is no conflict, but the platform still steps aside: the live path keeps
your file. If your `.list` points at a different repository — your own mirror, say — or the line is
commented out, the platform leaves everything alone.

If you replace the file on the live path yourself between installs, your choice is left untouched and
the saved copy of the previous configuration stays beside it under a name suffixed
`.vibe-node20-parked.disabled` — apt does not read it, and you can delete it by hand. **One
exception:** if you restore the configuration with the NodeSource install command itself rather than
with your own file, the platform cannot tell it from its own, removes it and puts the saved copy back
on the live path. On machines without a NodeSource configuration of their own, nothing changed.

### FIX-0912-6: the eighth unavailability reason now reaches self-hosted accounts as well

**Before**

In the `activation.marketTrial` block of [GET /v1/cowork/state](/docs/cowork/state) the reason `not_required_for_data` was returned only on cloud accounts whose platform access is opened by a paid plan of that kind, and only once the matching platform setting was enabled. A self-hosted account that was offered the trial (`available: true`) kept getting that offer with the setting enabled too.

**After**

The reason is still returned only on accounts whose platform access is opened by a paid plan of that kind, and only once the matching platform setting is enabled, but now on both account types: a self-hosted account that would be offered the trial gets `available: false` with reason `not_required_for_data` instead of `available: true` — the same as a cloud account. Without the setting the answer is unchanged on both types. The set of eight reasons is unchanged, and so is their precedence — a reason from the ladder, where one applies, still wins. Clients need to do nothing: the default branch on any `false` already hides the activation step.
