# Store. Остатки товара

> Источник: https://wiki.virtualpos.ru/hc/api/store  
> Обновлено: 2026-09-26

Методы для работы с остатками товаров на точках продаж: получение остатков по точкам продаж и по товарам, установка остатков одиночно и пачкой, обнуление остатков, которые не были обновлены при полной загрузке.

> **Примечание:**
> Как получить ключ, как формируются запросы и какие бывают ответы и ошибки — в статье [Общие сведения](https://wiki.virtualpos.ru/hc/api/overview). Здесь описаны только особенности методов раздела `store`.

## Методы

| Метод | Адрес | Что делает |
| --- | --- | --- |
| Остатки по точкам | `/api/store` | Возвращает остатки по одной или всем точкам продаж |
| Остатки товара | `/api/store/stock` | Возвращает остатки одного товара по всем точкам |
| Остатки всех товаров | `/api/store/byItem` | Возвращает остатки товаров по всем точкам продаж в сжатом виде |
| Установить остаток | `/api/store/updateOnhand` | Устанавливает остаток одного товара на точке продаж |
| Массовая установка остатков | `/api/store/multipleUpdateOnhand` | Устанавливает остатки пачкой |
| Версия остатков | `/api/store/getOnhandVersion` | Возвращает версию данных об остатках точки |
| Обнулить неизменённые | `/api/store/setZeroOnhand` | Обнуляет остатки, не обновлённые с указанной версии |

> **Важно:**
> **Где передавать параметры.** Ключ `apikey` и формат `format` принимаются и в адресной строке, и в теле POST-запроса. Параметры, которые указаны в описании методов как «Только GET» (это `warehouseid`, `itemid`, `article`, `itemname`, `stockonly`, `from` в методе `store`, параметры точки и товара в `updateOnhand`, `getOnhandVersion`, `setZeroOnhand` и `version` в `setZeroOnhand`), читаются только из адресной строки. Остальные параметры можно передавать и в адресной строке, и в теле POST. Значения с кириллицей и пробелами кодируйте.

## Остатки по точкам продаж

```
GET https://[компания].myvirtualpos.ru/api/store?apikey=MySecret&warehouseid=6&limit=1000&fields=itemname:price
```

### Параметры запроса

- **warehouseid** — Число. Ид точки продаж. Если не указан, возвращаются остатки всех точек. Только GET
- **itemid** — Число. Ид товара. Только GET
- **article** — Строка. Артикул товара. Только GET
- **itemname** — Строка. Точное название товара. Только GET
- **stockonly** — Значение по умолчанию — только товары с остатком больше нуля. Чтобы получить и нулевые остатки, передайте `stockonly=0`. Только GET
- **from** — Дата и время (например, `2026-09-20 10:00:00`). Остатки, изменившиеся или изменившие количество зарезервированного товара после этого момента. Только GET
- **from_id** — Число. Вернуть остатки с ид записи остатка больше указанного. Служит для порционной выдачи
- **limit** — Число, не более 1000. Максимальное количество остатков в ответе (для одной точки продаж). Без ограничения остатки собираются все
- **fields** — Дополнительные поля через двоеточие, например `itemname:price`. Допустимые значения ниже
- **format** — `json` (по умолчанию) или `xml`

Остатки возвращаются по возрастанию ид записи остатка. Если точка продаж с указанным `warehouseid` не найдена, вернётся ошибка `Data not found`. Точки продаж без подходящих остатков в ответ не попадают.

> **Важно:**
> **Выгружайте порциями.** Без `limit` метод собирает остатки по всем товарам в одном ответе; у точки продаж их могут быть десятки тысяч. Указывайте `warehouseid`, `limit` (например, 1000) и `from_id`: возьмите из ответа `last_id` и передайте его как `from_id` в следующем запросе, пока `record_count` меньше `limit`. Порционная выдача работает по одной точке продаж; без `warehouseid` поля `total` и `last_id` относятся к последней точке в ответе.

### Дополнительные поля (`fields`)

- **itemname** — Название товара (`name`)
- **article** — Артикул товара
- **cogs** — Закупочная цена остатка
- **expdate** — Срок годности остатка. Если не задан, приходит `0000-00-00`
- **price** — Цена товара в основном прайс-листе точки продаж
- **optionalprices** — Цены товара во всех прайс-листах: список `prices` с `pricelistid` и `price`
- **attributes** — Список значений характеристик остатка: `attribute` с `name` и `value`
- **turnovercalc** — Расчётная оборачиваемость товара на точке

Если указано неизвестное поле, метод вернёт ошибку со списком допустимых значений.

### Структура ответа

- **success** — `1` — данные получены
- **type** — Тип данных, всегда `store`
- **stockonly** — Значение параметра `stockonly`: `true` по умолчанию
- **total** — Число. Сколько остатков подходит под условия, без учёта `limit` (по последней обработанной точке)
- **record_count** — Число. Сколько остатков в ответе
- **count** — Число. Количество точек продаж в ответе
- **last_id** — Число. Ид записи последнего остатка в ответе. Используйте как `from_id`
- **warehouses** — Список точек продаж. Каждая обёрнута в объект `warehouse` с полями `id`, `name`, `count` и списком `items`

**Остаток (`item`)**

- **id** — Число. Ид товара
- **quantity** — Строка. Количество на точке продаж
- **available_quantity** — Число. Доступное количество: количество за вычетом зарезервированного в заказах
- **lot_number** — Строка. Серия (партия). Для товара без серии — пустая строка
- **name**, **article**, **cogs**, **expdate**, **price**, **prices**, **attributes**, **turnovercalc** — Дополнительные поля, если они запрошены в `fields`

### Пример ответа

```json
{
  "success": 1,
  "type": "store",
  "stockonly": true,
  "total": 6034,
  "record_count": 2,
  "count": 1,
  "last_id": 13027,
  "warehouses": [
    {
      "warehouse": {
        "id": 6,
        "name": "Точка на Тверской",
        "count": 2,
        "items": [
          { "item": { "id": 1084, "quantity": "2.000", "lot_number": "", "available_quantity": 2, "name": "Форма для наращивания", "price": "499.00" } },
          { "item": { "id": 795, "quantity": "65.000", "lot_number": "", "available_quantity": 65, "name": "Форма многоразовая", "price": "100.00" } }
        ]
      }
    }
  ]
}
```

Тот же ответ при `format=xml` (сокращённо):

```xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <success>1</success>
  <type>store</type>
  <stockonly>1</stockonly>
  <total>6034</total>
  <record_count>2</record_count>
  <count>1</count>
  <last_id>13027</last_id>
  <warehouses>
    <warehouse>
      <id>6</id>
      <name>Точка на Тверской</name>
      <count>2</count>
      <items>
        <item><id>1084</id><quantity>2.000</quantity><lot_number/><available_quantity>2</available_quantity></item>
      </items>
    </warehouse>
  </warehouses>
</root>
```

## Остатки одного товара

```
GET https://[компания].myvirtualpos.ru/api/store/stock?apikey=MySecret&id=795
```

- **id** — Число. Ид товара
- **external_id** — Строка. Код товара во внешней системе. Используется, если не указан `id`

Ответ содержит:

- **item** — Все поля товара (название, артикул, тип, ставка НДС, гибкие поля и другие)
- **warehouses** — Точки продаж, где у товара есть остаток. Каждая содержит поля точки продаж (ид, название, адрес, телефон, часы работы, код во внешней системе, гибкие поля) и список `stocks` с `stock`: `quantity`, `available_quantity`, `lot_number`

Возвращаются только положительные остатки. Если товар не найден, придёт ошибка `Item not found`.

## Остатки всех товаров по точкам

```
GET https://[компания].myvirtualpos.ru/api/store/byItem?apikey=MySecret&limit=1000&from_id=0&total=1
```

Компактная выгрузка «товар — остатки по всем точкам»: одна запись на товар.

- **id** — Число. Ид товара: вернуть только этот товар
- **from_id** — Число. Вернуть товары с ид больше указанного
- **limit** — Число. Максимальное количество товаров в ответе
- **total** — `1` — посчитать и вернуть общее количество товаров (`total`). Иначе `total` равно `null`

```json
{
  "success": 1,
  "type": "store_by_item",
  "last_id": 54,
  "count": 3,
  "total": 27168,
  "items": [
    { "id": 52, "quantities": "2=1;3=0;4=0;6=0" },
    { "id": 53, "quantities": "2=1;3=1;4=0;6=0" }
  ]
}
```

Поле `quantities` содержит остатки по точкам продаж в виде `ид_точки=количество` через точку с запятой: от количества вычтено зарезервированное. Товары возвращаются по возрастанию ид; `last_id` используйте как `from_id` в следующем запросе.

## Установка остатка

```
GET https://[компания].myvirtualpos.ru/api/store/updateOnhand?apikey=MySecret&warehouseid=6&itemid=52&quantity=20&cogs=125.5&lot_number=L1
```

### Параметры

- **warehouseid** — Число. Ид точки продаж. Нужно указать `warehouseid` или `ext_warehouseid`. Только GET
- **ext_warehouseid** — Строка. Код точки продаж во внешней системе. Только GET
- **itemid** — Число. Ид товара. Нужно указать `itemid` или `ext_itemid`. Только GET
- **ext_itemid** — Строка. Код товара во внешней системе. Только GET
- **quantity** — Число. Новое количество на точке продаж. Ноль допустим. Значение заменяет прежнее, а не прибавляется
- **cogs** — Число. Закупочная цена остатка. Для нового остатка без `cogs` цена равна нулю
- **lot_number** — Строка. Серия (партия). Остаток определяется парой «товар + серия»
- **manuf_date**, **expir_date** — Дата изготовления и срок годности остатка в формате `гггг-мм-дд`
- **attributes** — Массив характеристик остатка вида `[{"id":1,"value":"XL"}]` или `[{"ext_id":"size","value":"XL"}]`. Учитывается при поиске остатка и при его создании

Если остатка для такой пары нет, он создаётся. Остатки комплектов через API менять нельзя.

```json
{"success":1,"id":"171587","itemid":38334,"lot_number":"L1","warehouseid":15,"isnew":"1","quantity_before":0}
```

- **success** — `1` — остаток сохранён
- **id** — Ид записи остатка. При создании приходит строкой, при изменении — числом
- **itemid**, **warehouseid**, **lot_number** — Ид товара, ид точки продаж и серия
- **isnew** — `"1"` — остаток создан, `"0"` — изменён существующий
- **quantity_before** — Количество до изменения

## Массовая установка остатков

```
POST https://[компания].myvirtualpos.ru/api/store/multipleUpdateOnhand?apikey=MySecret
data=[{"warehouseid":6,"itemid":52,"quantity":20},{"ext_warehouseid":"WH-6","ext_itemid":"SKU-53","quantity":5,"lot_number":"L1"}]
```

Параметр `data` — JSON-массив. Каждый элемент содержит `warehouseid` или `ext_warehouseid`, `itemid` или `ext_itemid` и поля остатка, как в методе `updateOnhand`. Элементы обрабатываются независимо.

- **success** — `1` — запрос обработан (даже если часть остатков не сохранена)
- **success_count** — Количество сохранённых остатков
- **fails_count** — Количество остатков с ошибками
- **problem_ids** — Остатки, заданные по `itemid`, которые не удалось сохранить: объект `problem_item` с ид товара, без текста ошибки
- **problem_external_ids** — То же для остатков, заданных по `ext_itemid`: `problem_item` с полями `id` (код) и `message` (текст ошибки)

> **Важно:**
> Для остатков, заданных по `itemid`, текст ошибки не возвращается. Если нужно понять причину, повторите запрос для этого товара методом `updateOnhand`: он вернёт сообщение.

## Обнуление неизменённых остатков

Пара методов позволяет выгружать остатки «полным срезом»: после загрузки всех остатков точки нулевыми становятся те, которых в срезе не было.

1. Перед загрузкой запросите версию остатков точки продаж методом `getOnhandVersion`.
1. Загрузите остатки методами `updateOnhand` или `multipleUpdateOnhand`.
1. Вызовите `setZeroOnhand` с сохранённой версией: остатки точки, которые не обновлялись после этой версии, будут обнулены.

```
GET https://[компания].myvirtualpos.ru/api/store/getOnhandVersion?apikey=MySecret&warehouseid=6
GET https://[компания].myvirtualpos.ru/api/store/setZeroOnhand?apikey=MySecret&warehouseid=6&version=1790417774.000000
```

- **warehouseid** — Число. Ид точки продаж. Нужно указать `warehouseid` или `ext_warehouseid`. Только GET
- **ext_warehouseid** — Строка. Код точки продаж во внешней системе. Только GET
- **version** — Строка. Версия остатков из ответа `getOnhandVersion` (только для `setZeroOnhand`). Только GET

Ответы: `{"success":1,"version":"1790417774.000000"}` для `getOnhandVersion` и `{"success":1,"affected":4}` для `setZeroOnhand`, где `affected` — количество обнулённых остатков.

> **Важно:**
> **Обнуление необратимо и затрагивает все остатки точки продаж.** Метод обнуляет **все** остатки точки, изменённые до указанной версии: при неверной версии (например, `0`) под обнуление попадёт весь склад. Вызывайте `setZeroOnhand` только после полной загрузки среза и только с версией, полученной перед загрузкой.

## Примеры запросов

| Задача | Запрос |
| --- | --- |
| Первые 1000 остатков точки | `/api/store?apikey=MySecret&warehouseid=6&limit=1000` |
| Следующие 1000 остатков | `/api/store?apikey=MySecret&warehouseid=6&limit=1000&from_id=13027` |
| Остатки с ценой и названием | `/api/store?apikey=MySecret&warehouseid=6&limit=1000&fields=itemname:price` |
| Остатки, изменившиеся за сутки | `/api/store?apikey=MySecret&warehouseid=6&from=2026-09-25%2010:00:00` |
| Остатки товара по артикулу | `/api/store?apikey=MySecret&warehouseid=6&article=SH-1000&stockonly=0` |
| Остатки товара на всех точках | `/api/store/stock?apikey=MySecret&id=795` |
| Установить остаток | `/api/store/updateOnhand?apikey=MySecret&ext_warehouseid=WH-6&ext_itemid=SKU-52&quantity=20` |

## Ошибки

Ошибка авторизации и общие ошибки описаны в статье [Общие сведения](https://wiki.virtualpos.ru/hc/api/overview). Ошибки приходят с кодом HTTP 200 и `success` равным `0`:

| Сообщение | Метод | Причина | Что делать |
| --- | --- | --- | --- |
| Data not found | store | Точка продаж с таким `warehouseid` не найдена | Проверьте ид |
| Field '…' not found. Use combination of: […] | store | Неизвестное поле в `fields` | Используйте поля из списка |
| Item not found | stock, updateOnhand | Товар не найден | Проверьте ид или код |
| Warehouse not found | updateOnhand, getOnhandVersion, setZeroOnhand | Точка продаж не найдена | Проверьте ид или код |
| Can't update store data for kit ID=… | updateOnhand | Остатки комплектов менять нельзя | Меняйте остатки составляющих |
| Data validation failed. … | updateOnhand | Количество или другие значения не прошли проверку | Исправьте указанные поля |
| Cannot save data: … | updateOnhand | Ошибка сохранения | Текст после двоеточия поясняет причину |
| Could not create attribute: invalid format | updateOnhand | Характеристика без `id` или `ext_id` либо без `value` | Проверьте массив `attributes` |
| Invalid input format | multipleUpdateOnhand | `data` не JSON-массив | Передайте корректный JSON |
| Version parameter not specified | setZeroOnhand | Не передана `version` | Передайте версию из `getOnhandVersion` |

## Связанные разделы

- [Общие сведения](https://wiki.virtualpos.ru/hc/api/overview) — Ключ доступа, вызов методов, форматы ответов и ошибки
- [Item. Номенклатура](https://wiki.virtualpos.ru/hc/api/item) — Товары, остатки которых выгружаются
- [Warehouse. Точки продаж](https://wiki.virtualpos.ru/hc/api/warehouse) — Справочник точек продаж
- [Pricelist. Цены на товары](https://wiki.virtualpos.ru/hc/api/pricelist) — Прайс-листы, цены из которых возвращаются в `fields`
