API CalcRest для интеграций
Внешняя система по разрешению владельца читает ингредиенты, рецептуры, полуфабрикаты, граммовки, потери и КБЖУ. Цены и себестоимость — по отдельному праву. API только читает: записи в рецептуры и справочник нет ни в одном методе.
Рабочий API подключается на тарифах Про и Шеф. Песочница с демо-данными открыта всем без регистрации: интеграцию можно собрать и проверить до подключения клиента.
Песочница: попробуйте без ключа клиента
Песочница отвечает теми же методами, кодами ошибок и форматом, что и рабочий API. Только данные в ней не клиентские, а демо-кафе. Ключ публичный и один на всех.
| Адрес | https://calcrest.ru/api/public/sandbox/v1 |
| Ключ | cr_test_sandbox с правами на каталог и цены |
Проверьте подключение:
CALCREST_API_URL='https://calcrest.ru/api/public/sandbox/v1'
CALCREST_API_KEY='cr_test_sandbox'
curl -sS "$CALCREST_API_URL/connection" \
-H "Authorization: Bearer $CALCREST_API_KEY"В ответе придёт демо-кафе «Песочница CalcRest». С этими двумя переменными все команды быстрого старта ниже работают без правок: выгрузка, статус, скачивание, список рецептур и карточка.
Что лежит в демо-кафе
- Полуфабрикат «Тесто для блинов» и блюдо «Блины с ветчиной и сыром», где тесто стоит строкой-ПФ. На нём удобно проверить порядок импорта и цену ПФ.
- Салат «Цезарь» с курицей: потери при холодной и тепловой обработке, разделы состава и строка соуса без связи со справочником (
inline). - Сырники со сметаной в статусе
trial. - 11 позиций своего справочника с ценами и 3 позиции общего справочника (
master:201,master:202,master:204) без цены и с предупреждениемprice_missing.
Идентификаторы постоянные, их можно вшивать в автотесты. Например, блины: 5a0d2000-0000-4000-8000-000000000002, тесто: 5a0d2000-0000-4000-8000-000000000001.
Чем песочница отличается
- Данные фиксированные и одинаковые для всех.
- Выгрузка собирается примерно за 1,5 секунды и хранится 24 часа. После обновления CalcRest она может пропасть раньше: тогда создайте новую.
- Квот на выгрузки нет. Частота запросов ограничена по IP-адресу.
- Каждый ответ несёт заголовок
X-CalcRest-Environment: sandbox. Рабочий ключcr_live_…в песочнице не принимается, а тестовый не работает в рабочем API: придёт401с подсказкой.
Для перехода на данные клиента замените адрес на https://calcrest.ru/api/public/v1, а ключ на ключ клиента. Код интеграции менять не нужно.
Быстрый старт: получите всю базу
На выходе вы получите один JSON-файл: рецептуры блюд и полуфабрикатов, их состав, граммовки, потери, КБЖУ и справочник ингредиентов. Цены и себестоимость можно включить отдельно.
Создайте ключ в Профиле → Интеграции и API и подставьте его в переменную ниже. Примеры команд рассчитаны на Bash или Zsh. Для пробы на демо-данных возьмите адрес и ключ песочницы.
CALCREST_API_URL='https://calcrest.ru/api/public/v1'
CALCREST_API_KEY='ваш_API_ключ'1. Подготовьте выгрузку
Этот запрос запускает сборку файла со всей базой. CalcRest собирает связанные рецептуры и ингредиенты в одном состоянии: если во время скачивания кто-то изменит карточку, данные в файле останутся согласованными. В API такая выгрузка называется «снимок» базы.
REQUEST_ID=$(uuidgen)
curl -sS -X POST "$CALCREST_API_URL/exports" \
-H "Authorization: Bearer $CALCREST_API_KEY" \
-H "Idempotency-Key: $REQUEST_ID" \
-H "Content-Type: application/json" \
-d '{"selection":{"mode":"all"},"includePricing":false}'mode: "all" означает всю базу этого рабочего пространства. Предварительно получать список рецептур не нужно. Для выгрузки с ценами замените includePricing: false на true; у ключа должен быть включён доступ к ценам и себестоимости.
REQUEST_ID защищает от повторного создания одной выгрузки. При сетевом сбое повторите curl с тем же значением. Для новой выгрузки выполните REQUEST_ID=$(uuidgen) заново.
В ответе: номер выгрузки и её статус
{
"data": {
"id": "60000000-0000-4000-8000-000000000001",
"status": "queued",
"statusUrl": "/api/public/v1/exports/60000000-0000-4000-8000-000000000001"
}
}HTTP 202 означает, что запрос принят. Самого файла в этом ответе ещё нет. Сохраните data.id из вашего ответа как EXPORT_ID для следующих двух шагов.
2. Проверьте, готов ли файл
EXPORT_ID='вставьте_data.id_из_ответа'
curl -sS "$CALCREST_API_URL/exports/$EXPORT_ID" \
-H "Authorization: Bearer $CALCREST_API_KEY"Первый раз проверьте через 2 секунды. Если файл ещё готовится, повторяйте этот GET-запрос каждые 5 секунд. Если сервер вернул заголовок Retry-After, подождите не меньше указанного в нём числа секунд.
Сколько ждать? Время зависит от объёма базы и очереди. Фиксированного срока и оценки оставшегося времени API пока не возвращает. Две секунды — время до первой проверки, а не обещание готовности. Переходите к скачиванию, когда data.status станет ready.
queued— выгрузка ожидает своей очереди.processing— CalcRest собирает файл.ready— файл готов; вdata.contentUrlпоявится адрес скачивания.failed— собрать файл не удалось; причина вdata.problem. Остановите проверку и разберите ошибку.expired— срок хранения закончился; создайте новую выгрузку.
Этот запрос возвращает статус, даже если сборка завершилась ошибкой. Проверяйте data.status: одного HTTP 200 недостаточно. Повторно запускать шаг 1 во время ожидания не нужно.
3. Скачайте готовый JSON-файл
curl -fS --compressed "$CALCREST_API_URL/exports/$EXPORT_ID/content" \
-H "Authorization: Bearer $CALCREST_API_KEY" \
-o calcrest-export.jsonКоманда сохранит файл calcrest-export.json в текущую папку. Флаг --compressed распакует ответ сервера автоматически. В файле:
data.recipes— рецептуры блюд и полуфабрикатов с составом и расчётами.data.ingredients— пользовательский справочник и используемые позиции общего справочника.meta.capturedAt— момент, на который зафиксированы данные.warnings— замечания к исходным данным, например незаполненные значения.
Продукты без связи со справочником остаются внутри состава рецептуры с пометкой reference.type: "inline". При переносе их тоже нужно учитывать.
Готовый файл хранится 24 часа с момента готовности. Его можно скачать повторно тем же действующим ключом. Позже потребуется новая выгрузка; точный срок указан в data.expiresAt ответа на шаге 2.
Если нужны отдельные рецептуры
Список пригодится, чтобы показать пользователю его блюда и дать выбрать, какие перенести. Для выгрузки всей базы достаточно трёх шагов выше.
Получите список для выбора
curl -sS "$CALCREST_API_URL/recipes?limit=100" \
-H "Authorization: Bearer $CALCREST_API_KEY"В data придёт до 100 рецептур с ID, названием, статусом, категорией и числом строк состава (componentsCount). Состав, технология, КБЖУ и себестоимость в список не входят.Их можно получить для одной карточки следующим запросом или в JSON-выгрузке.
Если рецептур больше 100
API отдаёт длинный список частями. Например, 230 рецептур придут за три запроса: 100, ещё 100 и последние 30. В каждом ответе есть блок page:
{
"page": {
"hasMore": true,
"nextCursor": "строка_для_получения_следующей_части"
}
}hasMore: true означает, что есть продолжение. Скопируйте значение page.nextCursor целиком и передайте его в следующий запрос. Это готовая отметка «продолжить отсюда», её не нужно расшифровывать или составлять самостоятельно.
CURSOR='вставьте_page.nextCursor_из_последнего_ответа'
curl -sS --get "$CALCREST_API_URL/recipes" \
-H "Authorization: Bearer $CALCREST_API_KEY" \
--data-urlencode "limit=100" \
--data-urlencode "cursor=$CURSOR"Добавляйте data из каждого ответа к общему списку. Когда hasMore станет false, а nextCursor — null, вы получили всё. Если рецептур 100 или меньше, это произойдёт уже в первом ответе.
Каждая отметка продолжения действует 30 минут. Если она истекла, начните чтение списка с первого запроса. Используйте тот же API-ключ и те же фильтры: например, если искали q=суп, передавайте этот поиск и в следующих запросах. При смене фильтров начните новый список без cursor.
Получите одну карточку целиком
Возьмите id нужной рецептуры из списка:
RECIPE_ID='вставьте_id_рецептуры'
curl -sS "$CALCREST_API_URL/recipes/$RECIPE_ID" \
-H "Authorization: Bearer $CALCREST_API_KEY"В data придёт карточка с составом, граммовками, потерями, технологией и КБЖУ. Для цен добавьте к адресу ?includePricing=true и используйте ключ с доступом к ценам. Если в составе есть полуфабрикат, здесь будет ссылка на его ID; для переноса блюда со всеми вложенными полуфабрикатами используйте выгрузку.
Выгрузите только выбранные карточки
Выполните шаг 1 быстрого старта с новым REQUEST_ID, заменив тело запроса после -d на такое:
{
"selection": {
"mode": "ids",
"recipeIds": ["ID_первой_рецептуры", "ID_второй_рецептуры"]
},
"includePricing": false
}Подставьте реальные ID из списка. CalcRest добавит используемые полуфабрикаты, в том числе вложенные, и связанные ингредиенты автоматически. Затем проверьте готовность и скачайте файл по шагам 2 и 3.
Что означают поля
| Поле | Смысл |
|---|---|
basis | Всегда одна сохранённая расчётная норма. Состав и себестоимость уже приведены к ней. |
sourceServings | Число порций исходной карточки. Не множитель: умножать на него граммовки и стоимость нельзя, они уже приведены к норме. |
gross → afterCold → output | Закладка, масса после холодной обработки, масса после тепловой. Проценты применяются последовательно. Тепловые потери могут быть отрицательными: −150 % означает рост массы. |
outputSource | manual означает, что выход задан руками и заменяет расчётный; сохранённые проценты при этом остаются в ответе. |
reference.type | ingredient — позиция справочника (UUID или виртуальный master:<id>); recipe — полуфабрикат; inline — продукт без связи, требует сопоставления у вас. |
state | Полнота исходных данных для формулы. При partial известная часть стоимости лежит в knownSubtotal, а perRecipe равен null: неполная сумма не выдаётся как полная. |
null | Значение неизвестно. Ноль вместо неизвестности не подставляется никогда — ни для цены, ни для массы, ни для КБЖУ. |
Как импортировать корректно
- Разберите
data.ingredientsна пользовательские позиции (origin=user) и виртуальные позиции общего справочника (origin=referenced_master). - Покажите пользователю сопоставление
inline-строк со своей номенклатурой. Объединять их по названию нельзя: одинаковые имена у разных поставщиков — обычное дело. - Если у рецептуры заполнено
pairing, это техкарта и калькуляционная карта одного блюда двумя записями. Попросите пользователя выбрать вариант; автоматический импорт обоих создаёт дубль. - Сначала создайте простые полуфабрикаты, затем те, которые их используют, и после них блюда. Стоимость строки-полуфабриката уже учтена один раз — прибавлять к ней его сырьё нельзя.
- Сохраните соответствие
(workspaceId, entityType, id)→ ваш ID. Повторная выгрузка обновляет связанные объекты и не создаёт дублей. - Не удаляйте ничего автоматически. Отсутствие объекта в выборочной выгрузке,
404, сбой или окончание тарифа — не сигнал к удалению.
Проверяйте meta.catalogCoverage. Поле meta.ingredientSetComplete относится к справочнику, но не означает отсутствия inline-строк: считать data.ingredients полным перечнем сырья без этой проверки нельзя.
Ошибки и повторы
Ошибки приходят в формате application/problem+json (RFC 9457). Ветвитесь по полю code, а не по тексту. В каждом ответе есть requestId — приложите его к обращению в поддержку.
{
"type": "https://calcrest.ru/developers/api/errors#insufficient_scope",
"title": "API key lacks the required scope",
"status": 403,
"detail": "API key is missing the required scope: pricing:read.",
"instance": "/api/public/v1/ingredients",
"code": "insufficient_scope",
"requestId": "0b7c…"
}Повторяйте сетевые ошибки, 429 и 503 с экспоненциальной задержкой и джиттером, соблюдая Retry-After. Ответы 400, 401, 403, 404 и 422 бесконечно повторять бессмысленно — они не изменятся сами.
При повторе POST /exports после разрыва связи передавайте прежний Idempotency-Key: вы получите то же задание, а квота не спишется дважды. Новый UUID нужен для новой выгрузки.
Ограничения
- 60 запросов в минуту на ключ и 120 на рабочее пространство.
- 6 заданий экспорта в час и 60 в сутки на рабочее пространство.
- До 500 явно выбранных рецептур в
mode=ids. - Тело запроса до 256 KiB, файл выгрузки до 25 MiB до сжатия.
- Глубина вложенности полуфабрикатов до 20 уровней.
Чего API не делает
- Отдаёт текущую сохранённую рецептуру, а не содержание последней утверждённой ТТК по ГОСТ. Статус
approved— состояние карточки, не электронная подпись. - Не формирует PDF и XLSX: только структурированный JSON.
- Не пишет данные и не является двусторонней синхронизацией.
- Список рецептур содержит краткие сведения для выбора карточек. Для переноса базы используйте JSON-выгрузку: рецептуры, полуфабрикаты и ингредиенты в ней зафиксированы на один момент времени.
Вопросы по интеграции — в поддержку CalcRest с указанием requestId из ответа.