Первый чек за 5 минут
Приложение-мост принимает чеки по HTTP на том же компьютере, где стоит кассовая программа. Ваша программа отправляет чек — приложение пробивает его через фискальный модуль и возвращает фискальный признак и ссылку на QR.
1. Подготовьте кассу
Заголовок раздела «1. Подготовьте кассу»- Установите приложение и пройдите мастер настройки — установка.
- Откройте в приложении раздел «API» и скопируйте адрес и ключ. Адрес по умолчанию —
http://127.0.0.1:5600. В примерах ниже заменитеYOUR_API_KEYна свой ключ.
Проверьте, что приложение отвечает:
curl http://127.0.0.1:5600/v1/status -H "X-Api-Key: YOUR_API_KEY"В ответе configured: true — касса настроена, license.state — состояние подписки, fiscalDrive.connected: true — ФМ на месте.
2. Пробейте чек
Заголовок раздела «2. Пробейте чек»Две лепёшки по 5 000 сум и чай за 8 000 сум, скидка на чек 1 000 сум, оплата наличными 17 000 сум.
curl -X POST http://127.0.0.1:5600/v1/receipts \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Accept-Language: ru" \ -d '{ "externalId": "POS-2026-000123", "type": "sale", "items": [ { "name": "Лепёшка", "spic": "10901001001000000", "packageCode": "1500213", "quantity": 2000, "unitPrice": 500000, "vatPercent": 12 }, { "name": "Чай зелёный", "spic": "10901001001000000", "packageCode": "1500213", "quantity": 1000, "unitPrice": 800000, "vatPercent": 12 } ], "discount": 100000, "payments": { "cash": 1700000 } }'JavaScript (Node.js 18+ или браузерное приложение кассы)
Заголовок раздела «JavaScript (Node.js 18+ или браузерное приложение кассы)»const API = 'http://127.0.0.1:5600';const KEY = 'YOUR_API_KEY';
async function registerReceipt(receipt) { const response = await fetch(`${API}/v1/receipts`, { method: 'POST', headers: { 'X-Api-Key': KEY, 'Content-Type': 'application/json', 'Accept-Language': 'ru' }, body: JSON.stringify(receipt), }); const body = await response.json(); if (!response.ok) { // title и action — готовый текст для кассира, code — для лога throw Object.assign(new Error(body.error.title), { status: response.status, ...body.error }); } return body.result; // { terminalId, receiptSeq, fiscalSign, dateTime, qrCodeUrl }}
const result = await registerReceipt({ externalId: 'POS-2026-000123', type: 'sale', items: [ { name: 'Лепёшка', spic: '10901001001000000', packageCode: '1500213', quantity: 2000, unitPrice: 500000, vatPercent: 12, }, { name: 'Чай зелёный', spic: '10901001001000000', packageCode: '1500213', quantity: 1000, unitPrice: 800000, vatPercent: 12, }, ], discount: 100000, payments: { cash: 1700000 },});console.log('Фискальный признак:', result.fiscalSign, 'QR:', result.qrCodeUrl);using System.Net.Http.Json;
var http = new HttpClient { BaseAddress = new Uri("http://127.0.0.1:5600"), Timeout = TimeSpan.FromSeconds(60) };http.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY");http.DefaultRequestHeaders.Add("Accept-Language", "ru");
var receipt = new{ externalId = "POS-2026-000123", type = "sale", items = new[] { new { name = "Лепёшка", spic = "10901001001000000", packageCode = "1500213", quantity = 2000, unitPrice = 500000, vatPercent = 12 }, new { name = "Чай зелёный", spic = "10901001001000000", packageCode = "1500213", quantity = 1000, unitPrice = 800000, vatPercent = 12 }, }, discount = 100000, payments = new { cash = 1700000 },};
var response = await http.PostAsJsonAsync("/v1/receipts", receipt);var body = await response.Content.ReadFromJsonAsync<System.Text.Json.JsonElement>();if (!response.IsSuccessStatusCode){ var error = body.GetProperty("error"); // Показать кассиру title и action, записать в лог code Console.WriteLine($"{error.GetProperty("title")}: {error.GetProperty("action")} ({error.GetProperty("code")})"); return;}Console.WriteLine($"Фискальный признак: {body.GetProperty("result").GetProperty("fiscalSign")}");import jsonimport urllib.requestimport urllib.error
API = "http://127.0.0.1:5600"KEY = "YOUR_API_KEY"
def register_receipt(receipt: dict) -> dict: request = urllib.request.Request( f"{API}/v1/receipts", data=json.dumps(receipt).encode(), headers={"X-Api-Key": KEY, "Content-Type": "application/json", "Accept-Language": "ru"}, method="POST", ) try: with urllib.request.urlopen(request, timeout=60) as response: return json.load(response)["result"] except urllib.error.HTTPError as failure: error = json.load(failure)["error"] # title и action — для кассира, code — для лога raise RuntimeError(f'{error["title"]}: {error["action"]} ({error["code"]})') from None
result = register_receipt({ "externalId": "POS-2026-000123", "type": "sale", "items": [ {"name": "Лепёшка", "spic": "10901001001000000", "packageCode": "1500213", "quantity": 2000, "unitPrice": 500000, "vatPercent": 12}, {"name": "Чай зелёный", "spic": "10901001001000000", "packageCode": "1500213", "quantity": 1000, "unitPrice": 800000, "vatPercent": 12}, ], "discount": 100000, "payments": {"cash": 1700000},})print("Фискальный признак:", result["fiscalSign"], "QR:", result["qrCodeUrl"])3. Ответ
Заголовок раздела «3. Ответ»{ "externalId": "POS-2026-000123", "duplicate": false, "result": { "terminalId": "UZ170703100236", "receiptSeq": "1532", "fiscalSign": "287265419807", "dateTime": "2026-10-05 14:12:03", "qrCodeUrl": "https://ofd.soliq.uz/check?t=UZ170703100236&r=1532&c=20261005141203&s=287265419807" }}Сохраните result вместе с чеком: фискальный признак и QR печатаются на чеке, а terminalId, receiptSeq, fiscalSign и dateTime понадобятся для возврата.
Что дальше
Заголовок раздела «Что дальше»- Таймаут — не повод пробивать чек заново. Повторите запрос с тем же
externalId: приложение вернёт прежний результат. Почему это безопасно — повторы и externalId. - Возврат — тот же запрос с
"type": "refund"и полемrefundс данными исходного чека. - Ошибки — что показывать кассиру.
- Все запросы и поля — справочник API.
Таймаут запроса — 60 секунд. Обычно чек пробивается за 1–8 секунд, но если в этот момент приложение отправляет накопленные чеки в ОФД, чек ждёт своей очереди. Если ответа нет, повторите тот же запрос: приложение доведёт чек до конца и вернёт результат или ответит 409 BRIDGE_RECEIPT_IN_PROGRESS — тогда подождите несколько секунд и повторите ещё раз.