Fiscal Core API012 — справочник интеграции
Источник
Эта спека построена на официальной документации протокола API012 V1 (cashbox.ru / АРМАКС, 94 страницы, единственный авторитетный источник по контракту). Reverse-engineering APK использовался только для перекрёстной проверки (порты, broadcast’ы, hardware абстракция). Если PDF и APK расходятся — верь PDF.
Финальная проверка — только на K10 + Windows
Apple Silicon Mac не может запускать
armeabi-v7aAPK даже в эмуляторе (Google убрал 32-bit ARM bridge из всех arm64 system images). Для эмпирической проверки нужен либо физический K10, либо Windows/x86_64 Linux машина с ARM-эмулятором.
Архитектура процессов на устройстве
┌──────────────────────────────────────────────────────────┐
│ Unitodi K10-F (Android 8+, Centerm SmartPOS HAL) │
│ │
│ ┌────────────────┐ ┌──────────────────────┐ │
│ │ erp-pos │ │ pbfk10 (ФЯ) │ │
│ │ (React Native)│ │ ru.armax.cashbox. │ │
│ │ │ │ pbfk10 │ │
│ │ FiscalCore ├─HTTP───►│ CashboxHttpService │ │
│ │ Client │ :8088 │ :cashboxhttpservice │ │
│ │ │ /api012 │ proc │ │
│ │ │ /v1/ │ │ │
│ │ │ │ CashboxService │ │
│ │ │ │ :cashboxservice │ │
│ │ ◄─Bcast───│ proc │ │
│ │ │ status_ │ ↓ │ │
│ │ │ internal│ FsApi │ │
│ └────────────────┘ │ (Serial → ФН) │ │
│ └──────────┬───────────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ ФН │ │
│ │ (физический │ │
│ │ модуль ФНС) │ │
│ └──────┬───────┘ │
└─────────────────────────────────────────┼─────────────────┘
▼
┌─────────┐
│ ОФД │
└─────────┘
- ФЯ = Qt5 C++ приложение в двух процессах:
:cashboxserviceproc(фискальная логика, обмен с ФН и ОФД) и:cashboxhttpserviceproc(HTTP сервер для внешних клиентов) - Java-обёртка минимальная: shim’ы Activity/Service +
FsApi(прямой Serial-IO в ФН черезcom.pos.sdk.exserialport.ExSerialPortDevice) +ServiceStarter(autostart на BOOT_COMPLETED) - Hardware HAL — Centerm SmartPOS SDK (
com.centerm.smartpos.aidl.*,com.pos.sdk.*), общий для всего семейства SmartPOS-устройств - Касса не лезет в ФН напрямую — только через HTTP API ФЯ
Транспорт
| Параметр | Значение |
|---|---|
| Протокол | HTTP/1.1 |
| Порт по умолчанию | 8088 |
| Базовый путь | /api012/v1/ |
| Content-Type | application/json (с UTF-8) |
| HTTP методы | GET, POST |
| Авторизация | HTTP Basic, login/password = креды кассира из локальной базы ФЯ |
| Идемпотентность | Параметр externalId — повторный запрос с тем же ID вернёт результат предыдущего |
Авторизация — это креды кассира ФЯ, не сервисный токен
У ФЯ есть своя локальная база кассиров с правами (см.
loadcashiers.json). Каждый запрос требует Basic Auth с логином/паролем кассира из этой базы. Ролей нет — есть гранулярныеpermissions(Receipts, CycleClose, Reports, Settings…).Для нашей интеграции: заранее создаём в ФЯ одного «сервисного» кассира с полными правами через
POST savecashier.json, креды кладём в env-переменные erp-pos. Реальное ФИО кассира из нашего PIN-логина передаём в каждом запросе как полеcashierвdocument.
Контракт ответа
Любой ответ — JSON-объект с реквизитом result:
result | Значение |
|---|---|
0 | Успех |
1..1535 | Бизнес-ошибка ФЯ (см. таблицу ошибок) |
64 | Документ с таким externalId уже существует — это нормальный случай идемпотентности, надо забрать готовый документ |
33554432..33555432 | HTTP-статус не 200 — result & 0xFFF = HTTP-статус, message транслитом |
HTTP 200 + result=0 ⇒ операция выполнена. Если ответ не содержит result поля — значит ответил не ФЯ (кто-то другой захватил порт).
Общие query-параметры
| Параметр | Тип | Назначение |
|---|---|---|
externalid | string | Идемпотентность (≤255 байт). Если не передать — ФЯ сгенерирует |
clientId | string | Идентификатор клиентского приложения, эхо в ответе |
silent | 0|1 | 1 — не печатать чек на принтере (для электронных чеков) |
brief | 0|1 | 1 — краткая печатная форма отчёта |
short | 0|1 | 1 — короткий формат документа в архиве |
fd | int | Номер фискального документа |
Регистр имени параметра
В документации в одном месте
externalid, в другомexternalId. На реальном устройстве проверить, какое имя действительно работает (или оба). Для входных JSON в body —externalId(camelCase).
Эндпоинты
Авторизация и кассиры
| Метод | Путь | Назначение |
|---|---|---|
| GET | /api012/v1/login.json | Текущий кассир + permissions |
| GET | /api012/v1/loadcashiers.json | Список кассиров (≤200, пагинация по cashierId) |
| GET | /api012/v1/removecashier.json?cashierId=N | Удалить кассира |
| POST | /api012/v1/savecashier.json | Создать/обновить кассира |
Permissions (флаги в cashier.permissions):
Corrections, CycleClose, CycleOpen, Fiscalization, OfflineNotifications, Receipts, Reports, ReturnCorrections, ReturnReceipts, Settings, WriteSerial.
Состояние ККТ
| Метод | Путь | Назначение |
|---|---|---|
| GET | /api012/v1/cashboxstatus.json | Полный статус (dev, fs, model, reg) — основной мониторинг |
| GET | /api012/v1/cashboxstatus.html | То же, HTML для отладки |
| GET | /api012/v1/cashboxserial.json | Серийник + модель |
| GET | /api012/v1/fscounters.json | Счётчики ФН за всё время |
| GET | /api012/v1/cyclecounters.json | Счётчики текущей смены |
Структура cashboxstatus.json (ключевое для UI кассы)
{
"result": 0,
"status": {
"dev": {
"accumLevel": "55.0%", // заряд аккумулятора
"ipAddresses": "192.168.0.13",
"versionText": "0.0.30",
"printerStatus": 0 // 0 = ОК, иначе ошибка
},
"dt": "2022-03-01T12:01:53+03:00",
"fs": {
"availableDocs": 249785, // сколько ещё документов влезет в ФН
"currentFfd": "1.2",
"cycle": 7, // номер текущей смены
"cycleCloseDt": null,
"cycleIsOpen": true, // ◄─ ключевое для UX
"cycleOpenDt": "2021-11-29T11:00:00+03:00",
"freeBufSize": 14068, // КБ свободно для документов 30 хранения
"labelBufUsed": 1, // % заполнения буфера маркировок
"lifeTime": {
"expiredDays": 465, // дней до окончания срока действия ФН
"expiredDt": "2023-03-09",
"regsCount": 2,
"availableRegs": 28
},
"notifications": {
"firstNotification": 0,
"memFullPercent": 1,
"notSendedCount": 0, // ◄─ не отправлено в ОИСМ
"status": 0
},
"receipt": 0, // чеков в смене
"release": "ФН-1.1М МГМ ...",
"status": {
"lastFd": 26, // номер последнего ФД
"phase": 3, // фаза жизни ФН
"serial": "9999078902009303"
},
"transport": {
"firstDocDt": null,
"firstDocNumber": 0,
"messagesCount": 0, // ◄─ не отправлено в ОФД
"ofdMessageReading": false,
"status": 0
}
},
"hasNotPrinted": false, // ◄─ есть недопечатанный документ — позвать printnotprinted.json
"model": {
"model": 211,
"modelName": "ЛИМОН БАНК-Ф",
"serial": "11200401",
"version": "012"
},
"reg": { /* регистрационные данные ККТ */ }
}
}Что слушать в касса: cycleIsOpen, printerStatus, hasNotPrinted, notSendedCount, messagesCount, expiredDays, availableDocs. Из этих полей собирается «здоровье ККТ» в шапке экрана кассира.
Управление сменой
| Метод | Путь | Назначение |
|---|---|---|
| GET | /api012/v1/cycleopen.json | Открыть смену (минимальный вариант, без доп.данных) |
| POST | /api012/v1/cycleopen.json | Открыть смену (с cashier, additionalParam, header, footer) |
| GET | /api012/v1/cycleclose.json | Закрыть смену (Z-отчёт) |
| POST | /api012/v1/cycleclose.json | Закрыть смену (с doBankSettlement) |
| GET | /api012/v1/xreport.json | X-отчёт (без обнуления, нефискальный) |
| GET | /api012/v1/fsreport.json | Отчёт ФН (нефискальный) |
POST cycleopen.json — body
{
"document": {
"cashier": "Иванова Степанида Карловна",
"additionalParam": "CYCLE_PARAM",
"additionalData": "21FFCE01",
"header": "Текст заголовка",
"footer": "Текст подвала"
}
}POST cycleclose.json — body
{
"document": {
"cashier": "Иванова Степанида Карловна",
"additionalParam": "CYCLE_PARAM",
"additionalData": "21FFCE01",
"header": "Текст заголовка",
"footer": "Текст подвала",
"doBankSettlement": true, // банковская сверка при закрытии
"bankSettlementErrorIsCycleClosingError": true // блокировать Z-отчёт если сверка не прошла
}
}Ответ Z-отчёта содержит полные cycleCounters с разбивкой по debit/debitRefund/credit/creditRefund × vat0/10/10_110/20/20_120/none × cash/card/prepay/postpay/barter. Эти счётчики надо отправлять в наш бэкенд для отчётности.
Чеки
| Метод | Путь | Назначение |
|---|---|---|
| POST | /api012/v1/receipt.json | Регистрация фискального чека (приход/расход/возвраты) |
| POST | /api012/v1/correction.json | Чек коррекции (то же тело + обязательное correctionReason) |
| GET | /api012/v1/introduction.json?cash=N | Внесение наличных (нефискальное) — N в копейках |
| GET | /api012/v1/payout.json?cash=N | Выплата/изъятие наличных — N в копейках |
POST receipt.json — body
{
"document": {
"cashier": "СИС. АДМИН",
"cashierInn": "",
"address": "Тульская область, деревня Селезневка, Аэропорт",
"place": "Окно 13",
"tax": 1, // система налогообложения (см. таблицу)
"paymentAttr": 1, // 1=приход, 2=возврат прихода, 3=расход, 4=возврат расхода
"buyerPhone": "client@example.com",
"internetPayment": false,
"header": "Текст заголовка",
"footer": "Текст подвала",
"ctext": "Текст внутри чека, перед позициями",
"operations": [ // позиции БЕЗ маркировки
{
"name": "Кофе",
"type": 1, // признак предмета расчёта (см. таблицу)
"paymentType": 4, // признак способа расчёта (см. таблицу)
"price": "455.00", // цена за единицу — СТРОКА В РУБЛЯХ
"quantity": "1.000", // количество — строка с дробью
"vatRate": 1, // ставка НДС (см. таблицу)
"productCode": { // штрихкод (опционально)
"type": 1302, // EAN-13
"data": "4607001770459"
},
"agentFlag": 64, // если позиция от агента (опционально)
"providerInn": "7725225244",
"providerData": { "name": "ООО ТУГРИК", "phones": ["+723685555"] },
"countryCode": "Uz",
"declarationNumber": "Gyjknnj"
}
],
"labledOperations": [ // позиции С маркировкой (отдельный список!)
// та же структура + дополнительно
// checkLabelFlags, labelCheckResult (из labelcheck.json),
// industryProperties, fraction
],
"cash": "455.00", // суммы оплаты по типам (строки в рублях)
"card": "0.00",
"prepay": "0.00",
"postpay": "0.00",
"barter": "0.00",
"cardPayments": [ // тег ФФД 1234 — детали безналичных оплат
{
"amount": "455.00", // сумма (строка в рублях)
"wayType": 1, // 0..255 способ безналичной оплаты
"ids": "RRN-25434534534", // идентификаторы (≤256 байт)
"additional": "auth=12345" // доп.данные (≤256 байт)
}
],
"iBank": { // ОПЦИОНАЛЬНО — встроенный эквайринг (см. ниже)
"usePosFeature": true,
"rrn": "25434534534",
"transactionId": "25434534534",
"authCode": "25434534534",
"merchantNumber": 1,
"bankClientApp": "ru.example.pos"
}
}
}Ответ receipt.json
{
"result": 0,
"document": {
/* всё что было в запросе + */
"docNumber": 39, // тег 1040 — номер ФД
"fsNumber": "9999078902009303", // тег 1041 — номер ФН
"fiscalCode": "1335618438", // тег 1077 — ФПД
"qr": "<строка для QR>", // тег 1196
"dt": "2021-11-30T10:09:26+03:00",
"cycle": 11, // номер смены
"receipt": 1, // номер чека за смену
"externalId": "La/IGZVl3tFr0r0qV8hmUdylwAA=",
"ftsSite": "nalog.ru",
"vat20": "75.83" // авторасчёт НДС от ФЯ
}
}Все суммы — строки рублей с двумя десятичными
Цены, итоги, НДС, оплаты — всё передаётся как строки вида
"455.00". НЕ копейки, НЕ числа. Это касаетсяprice,quantity,cost,cash,card,prepay,postpay,barter,vat20,vat10,amountвcardPaymentsи т.д.Исключение:
introduction.jsonиpayout.jsonберутcashв query-параметре в копейках (?cash=100000= 1000 ₽). Это легаси GET-эндпоинты старого стиля.
Справочные таблицы
paymentAttr (тег 1054, признак расчёта):
| Код | Значение |
|---|---|
| 1 | Приход (продажа) |
| 2 | Возврат прихода |
| 3 | Расход |
| 4 | Возврат расхода |
tax (тег 1055, СНО):
| Код | Значение |
|---|---|
| 1 | ОСН |
| 2 | УСН доход |
| 4 | УСН доход минус расход |
| 8 | ЕНВД |
| 16 | ЕСХН |
| 32 | ПСН |
vatRate (тег 1199, ставка НДС):
| Код | Ставка |
|---|---|
| 1 | 20% |
| 2 | 10% |
| 3 | 20/120 (расчётная) |
| 4 | 10/110 (расчётная) |
| 5 | 0% |
| 6 | НДС не облагается |
| 7 | 5% |
| 8 | 7% |
| 9 | 5/105 |
| 10 | 7/107 |
| 11 | 22% |
| 12 | 22/122 |
paymentType (тег 1214, признак способа расчёта):
| Код | Значение |
|---|---|
| 1 | Полная предоплата 100% |
| 2 | Частичная предоплата |
| 3 | Аванс |
| 4 | Полный расчёт (для общепита — это то что нам нужно) |
| 5 | Частичная оплата + кредит |
| 6 | Передача в кредит |
| 7 | Оплата кредита |
type (тег 1212, признак предмета расчёта):
Для общепита нас интересуют:
| Код | Значение |
|---|---|
| 1 | Товар |
| 2 | Подакцизный товар |
| 3 | Работа |
| 4 | Услуга |
| 13 | Иной предмет расчёта |
| 30..33 | Маркированные/немаркированные товары и подакцизные с КМ |
Полная таблица 27 значений (включая лотереи, ставки, агентские) — в PDF стр. 70-73.
productCode.type (типы кодов товара):
| Код | Тип |
|---|---|
| 1300 | Неизвестный |
| 1301 | EAN-8 |
| 1302 | EAN-13 |
| 1303 | ITF-14 |
| 1304 | GS1.0 |
| 1305 | GS1.M |
| 1306 | КМК |
| 1307 | МИ |
| 1308 | ЕГАИС-2.0 |
| 1309 | ЕГАИС-3.0 |
| 1320..1325 | Ф.1..Ф.6 |
unit (тег 2108, мера количества):
| Код | Ед. |
|---|---|
| 0 | шт./ед. |
| 10..12 | г / кг / т |
| 20..22 | см / дм / м |
| 30..32 | см² / дм² / м² |
| 40..42 | мл / л / м³ |
| 50..51 | кВт·ч / Гкал |
| 70..73 | сутки / час / мин / с |
| 80..83 | КБ / МБ / ГБ / ТБ |
| 255 | иное |
POST correction.json
То же тело что у receipt.json + обязательное correctionReason:
{
"document": {
/* ... все поля как у receipt ... */
"correctionReason": {
"isIndependent": true, // true=независимая, false=по предписанию
"date": "2021-11-30", // дата корректируемого расчёта
"docNumber": "12345" // номер предписания налогового органа (если есть)
}
}
}Маркировка (Честный знак)
Последовательность для каждого чека с маркой:
GET /api012/v1/startlabelschecksession.json— очистить таблицу проверок марок в ФН (макс 128 марок на чек!)- Для каждого маркированного товара:
POST /api012/v1/labelcheck.jsonсrawLabelиexcpectedStatus - Результаты подложить в
labelCheckResultсоответствующих позиций вlabledOperations POST /api012/v1/receipt.jsonилиPOST /api012/v1/correction.json
Полный контракт labelcheck.json см. в PDF стр. 83-86. Для MVP общепита — Phase 2+.
Архив ФН
| Метод | Путь | Назначение |
|---|---|---|
| GET | /api012/v1/ofdticket.json?fd=N | Квитанция ОФД по номеру ФД |
| GET | /api012/v1/findfsdoc.json?fd=N | Найти документ в ФН |
| GET | /api012/v1/tlvdoc.json?fd=N | TLV-представление документа (доступно 30 дней) |
| GET | /api012/v1/regdoc.json?fd=N&short=0|1 | Документ о регистрации/перерегистрации |
Печать и изображения
| Метод | Путь | Назначение |
|---|---|---|
| POST | /api012/v1/print/<name>.bb | Печать произвольного BBCode-текста (body = текст в UTF-8) |
| POST | /api012/v1/images/<fileName> | Загрузить изображение в ККТ |
| GET | /api012/v1/images/<fileName> | Скачать изображение |
| GET | /api012/v1/images/list.json | Список загруженных изображений |
| GET | /api012/v1/printnotprinted.json | Допечатать недопечатанный документ (hasNotPrinted=true в статусе) |
Регистрация ККТ (разово)
| Метод | Путь | Назначение |
|---|---|---|
| POST | /api012/v1/preprintregistration.json | Предпечать отчёта о регистрации |
| POST | /api012/v1/registrate.json | Регистрация в ФНС |
| POST | /api012/v1/calcreport.json | Расчётный отчёт |
| GET | /api012/v1/fsclose.json | Закрытие архива ФН |
Это бухгалтерские операции. Наша касса их не делает — выполняются разово через UI ФЯ при пусконаладке.
Встроенный эквайринг (iBank)
Опционально и не для всех ККТ
Документация явно квалифицирует: «Объект только для андроидных касс с банковским терминалом». То есть это класс оборудования (например, ЛИМОН БАНК-Ф из примеров PDF, где касса+эквайринг в одном корпусе), а не общая фича. Применимо ли к Unitodi K10-F — надо проверить эмпирически на физическом устройстве. В strings APK строки
iBank,usePosFeature,bankClientAppне найдены, что косвенно указывает на отсутствие реализации в этом конкретном билде.
В чек продажи (receipt.json) можно добавить опциональный объект iBank. Когда он передан и card > 0, ФЯ самостоятельно:
- Запускает встроенное банковское приложение для оплаты картой
- Ждёт результата авторизации
- Если успех — фискализирует чек
- Запускает приложение из
bankClientApp(например, нашу кассу) для возврата фокуса
"iBank": {
"usePosFeature": true, // true → запустить оплату картой на встроенном POS-терминале
"rrn": "25434534534", // только для возвратов — RRN исходной операции
"transactionId": "25434534534", // только для возвратов
"authCode": "25434534534", // только для возвратов
"merchantNumber": 1, // если у POS несколько мерчантов
"bankClientApp": "ru.example.pos" // куда вернуться после операции
}Параллельный механизм: при закрытии смены cycleclose.json можно передать doBankSettlement: true — ФЯ автоматически выполнит сверку итогов с банком.
Подробное сравнение iBank-режима с внешней оркестрацией эквайринга через нашу кассу — в [[08-Specs/POS/Sale Flow|Sale Flow]].
Коды ошибок
Полный перечень в PDF стр. 91-94. Ключевые для нашей логики:
Авторизация и доступ
| Код | Описание | Действие кассы |
|---|---|---|
| 0 | Без ошибок | — |
| 4 | Некорректные данные кассира | Перепроверить креды в env |
| 14 | Доступ запрещён | Кассиру нет нужного permission |
Состояние кассы
| Код | Описание | Действие кассы |
|---|---|---|
| 23 | ККТ не фискализирована | Нужна разовая регистрация через UI ФЯ |
| 24 | Смена уже открыта | OK для cycleopen (повтор) — игнор |
| 25 | Смена закрыта | Открыть смену перед чеком |
| 26 | Смена превысила 24 часа | Заблокировать UI кассы, пока не закроют смену |
| 32 | В ФН есть неотправленные документы | Баннер «не отправлено в ОФД: N», операции продолжать |
Чек / суммы
| Код | Описание | Действие кассы |
|---|---|---|
| 28 | Сумма картой превышает сумму чека | Проверить расчёт оплаты |
| 29 | Недостаточно средств для оплаты чека | Доплатить |
| 33 | Не хватает реквизитов | Логировать, дёрнуть тех.поддержку |
| 45 | В чеке нет предметов расчёта | Не должно случаться при валидации |
| 46 | Чек не оплачен | cash + card + … < amount |
| 47 | Переплата по чеку | cash + card + … > amount |
| 48 | Некорректная налоговая система | Проверить tax |
| 49 | Некорректная сумма по чеку | Проверка |
Идемпотентность
| Код | Описание | Действие кассы |
|---|---|---|
| 64 | Документ с таким externalId уже существует | Это нормально. Запросить готовый документ через findfsdoc.json |
Принтер
| Код | Описание | Действие кассы |
|---|---|---|
| 65 | Есть недопечатанный документ | Вызвать printnotprinted.json |
| 975, 1000-1011 | Ошибки принтера | Кассиру: «нет бумаги / перегрев / ошибка», деталь по коду |
| 1005 | Нет бумаги | Прямой алерт «вставьте ленту» |
ФН
| Код | Описание | Действие кассы |
|---|---|---|
| 1281-1342 | Ошибки ФН (раскодированные команды ФН → human-readable текст в message) | Логировать, эскалировать |
| 1297 | Нет транспортного соединения | ФН не отвечает |
| 1298 | Исчерпан ресурс ФН | Замена ФН — критично |
| 1302 | Превышена продолжительность смены | Аналогично коду 26 |
| 1533-1535 | Ошибки разбора/связи с ФН | Тех.поддержка |
HTTP-обёртка
| Код | Описание |
|---|---|
| 33554432-33555432 | HTTP-статус не 200, result & 0xFFF = HTTP, message транслитом |
Open questions для проверки на K10
- Порт по умолчанию — действительно
8088на K10? (PDF говорит 8088, но в UI ФЯ может быть переопределён) - Логин/пароль кассира по умолчанию — спросить у поставщика или прочитать из
login.jsonчерез UI ФЯ -
iBank.usePosFeatureработает ли в этом билде? Послатьreceipt.jsonсcard>0иiBank.usePosFeature=true→ реально ли запустится банковское приложение? (Нужно предварительно установить какое-то банковское приложение, например INPAS Smart Sale или сберовский Pay) - Список
bankClientApp— какие банковские APK поддерживаются интеграцией iBank? Не указано в PDF -
/print/<name>.bbBBCode синтаксис — приложение к PDF, в выгруженном тексте отсутствует. Скачать оригинал или экспериментировать - Регистр имени параметра
externalidvsexternalIdв query — оба или один? - Поведение при
cycle > 24h— точно ли коды 26 и 1302 эквивалентны -
offlineNotifications.bin/.zip— формат файла для ОИСМ в автономном режиме (нам не нужно для MVP, но зафиксировать)
Связанное
- Sale Flow — пошаговый сценарий продажи с двумя архитектурами эквайринга и failure modes
- Репозитории —
erp-pos - Архитектура системы