{
	"openapi": "3.0.3",
	"info": {
		"title": "ChequeChecker API",
		"version": "1.0.0",
		"summary": "Перевірка фіскальних чеків у реєстрі ДПС",
		"description": "REST API для перевірки фіскальних чеків у реєстрі Державної податкової служби України.\n\nСервіс приймає реквізити чека, звертається до реєстру ДПС, зберігає відповідь і віддає її у форматі JSON. Успішно знайдені чеки кешуються: повторний запит тих самих реквізитів обслуговується з локальної бази миттєво і **не списує баланс**.\n\n**Тарифікація.** Кожне звернення до реєстру списує з балансу 1 запит, якщо чек знайдено, і 0,5 запиту, якщо реєстр відповів помилкою. Відповідь із кешу безкоштовна. Коли залишок падає нижче 1, сервіс перестає звертатися до реєстру і повертає `success: false`.",
		"contact": {
			"name": "ChequeChecker",
			"url": "https://chequechecker.tech/"
		}
	},
	"servers": [
		{
			"url": "https://chequechecker.tech",
			"description": "Продакшн"
		}
	],
	"security": [
		{
			"basicAuth": []
		}
	],
	"tags": [
		{
			"name": "Чеки",
			"description": "Перевірка фіскальних чеків"
		},
		{
			"name": "Баланс",
			"description": "Облік витрачених запитів"
		}
	],
	"paths": {
		"/cheque_info": {
			"get": {
				"tags": [
					"Чеки"
				],
				"operationId": "getChequeInfo",
				"summary": "Отримати дані фіскального чека",
				"description": "Повертає чек за його реквізитами. Усі чотири параметри обовʼязкові й мають точно збігатися з даними на чеку або в його QR-коді — реєстр шукає за повним збігом.\n\nЯкщо такий чек уже успішно перевірявся раніше, відповідь віддається з локального кешу (`fromDFS: false`) без списання балансу. Щоб примусово перезапитати реєстр, передайте `forced=1`.",
				"parameters": [
					{
						"name": "url",
						"in": "query",
						"required": false,
						"description": "Посилання з QR-коду чека — альтернатива чотирьом параметрам вище. Сервіс сам дістане з нього `id`, `fn`, `sm` і склеїть `date` з окремих `date` та `time`. Якщо параметр передано разом із явними — явні мають перевагу.\n\nРозпізнаються обидва варіанти, які трапляються на практиці: числовий `id` з `time=1431` і рядковий `id` з `time=12%3A57%3A37`. Пробіл-роздільник тисяч у сумі (зокрема `%20` та нерозривний) прибирається, кома як дробовий роздільник замінюється крапкою. Зайві параметри на кшталт `mac` ігноруються.",
						"schema": {
							"type": "string"
						},
						"example": "https://cabinet.tax.gov.ua/cashregs/check?id=PDgeh4_KHik&date=20260910&time=12%3A57%3A37&fn=4001127837&sm=471.00"
					},
					{
						"name": "id",
						"in": "query",
						"required": false,
						"description": "Ідентифікатор чека — фіскальний номер документа. У QR-коді чека це параметр `id`. Обовʼязковий, якщо не передано `url`.",
						"schema": {
							"type": "string"
						},
						"example": "2037032"
					},
					{
						"name": "date",
						"in": "query",
						"required": false,
						"description": "Дата й час чека у форматі `YYYY-MM-DD HH:MM:SS`. Береться з чека; секунди зазвичай нульові. Значення потребує URL-кодування (пробіл → `%20`). Обовʼязковий, якщо не передано `url`.",
						"schema": {
							"type": "string",
							"pattern": "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$"
						},
						"example": "2026-09-10 09:25:00"
					},
					{
						"name": "fn",
						"in": "query",
						"required": false,
						"description": "Фіскальний номер РРО або ПРРО, на якому видано чек — 10 цифр. Обовʼязковий, якщо не передано `url`.",
						"schema": {
							"type": "string"
						},
						"example": "3001164740"
					},
					{
						"name": "sm",
						"in": "query",
						"required": false,
						"description": "Загальна сума чека в гривнях. Роздільник дробової частини — крапка. Обовʼязковий, якщо не передано `url`.",
						"schema": {
							"type": "string"
						},
						"example": "270.0"
					},
					{
						"name": "forced",
						"in": "query",
						"required": false,
						"description": "Передайте `1`, щоб ігнорувати кеш і перезапитати реєстр. Таке звернення завжди списує баланс. Будь-яке інше значення або відсутність параметра означає звичайний режим із кешем.",
						"schema": {
							"type": "string",
							"enum": [
								"1"
							]
						},
						"example": "1"
					}
				],
				"responses": {
					"200": {
						"description": "Запит оброблено. Перевіряйте поля `success` та `error`, щоб зрозуміти результат.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ChequeInfoResponse"
								},
								"examples": {
									"found": {
										"summary": "Чек знайдено (з реєстру)",
										"value": {
											"success": true,
											"errorMessage": null,
											"message": null,
											"fromDFS": true,
											"error": null,
											"error_description": null,
											"check": "ICAgICAgICDQotCe0JIgwqvQnNCY0JTQm8K7DQo...",
											"checkXml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CHECK>...</CHECK>"
										}
									},
									"cached": {
										"summary": "Чек віддано з кешу (баланс не списано)",
										"value": {
											"success": true,
											"errorMessage": null,
											"message": null,
											"fromDFS": false,
											"error": null,
											"error_description": null,
											"check": "ICAgICAgICDQotCe0JIgwqvQnNCY0JTQm8K7DQo...",
											"checkXml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CHECK>...</CHECK>"
										}
									},
									"notFound": {
										"summary": "Реєстр не знайшов чек",
										"value": {
											"success": true,
											"errorMessage": null,
											"message": null,
											"fromDFS": true,
											"error": "Помилка",
											"error_description": "Інформація відсутня Не знайдено.",
											"check": null,
											"checkXml": null
										}
									},
									"noBalance": {
										"summary": "Баланс вичерпано",
										"value": {
											"success": false,
											"errorMessage": "No balance. Current balance left 0.0",
											"message": null
										}
									},
									"missingParameter": {
										"summary": "Не передано обовʼязковий параметр",
										"value": {
											"success": false,
											"errorMessage": "fn parameter is not set",
											"message": null
										}
									}
								}
							}
						}
					},
					"401": {
						"$ref": "#/components/responses/Unauthorized"
					}
				}
			}
		},
		"/cheque_structured": {
			"get": {
				"tags": [
					"Чеки"
				],
				"operationId": "getChequeStructured",
				"summary": "Чек у структурованому вигляді",
				"description": "Той самий чек, що й `/cheque_info`, але вже розібраний: продавець, позиції, оплати та податки окремими полями.\n\nРеєстр віддає чек у двох різних діалектах XML — компактному `<RQ>` і розлогому `<CHECK>` — усередині base64, з кодуванням, яке міняється від документа до документа. Цей ендпоінт зводить обидва до однієї структури, тож розбирати нічого не потрібно.\n\n**Тарифікація й кеш ті самі**, що й у `/cheque_info`: той самий запит до реєстру, те саме списання, той самий кеш.\n\nЗверніть увагу на `cheque.seller.source`. Формат `<RQ>` **не містить** назви продавця, точки й адреси, тому вони беруться: `xml` — зі структури `<CHECK>`; `certificate` — із сертифіката підписувача всередині підпису PKCS#7 (там повна юридична назва, ЄДРПОУ й адреса); `printed` — із шапки друкованої форми, і це вже здогадка за версткою, а не структуровані дані.",
				"parameters": [
					{
						"name": "url",
						"in": "query",
						"required": false,
						"description": "Посилання з QR-коду чека — альтернатива чотирьом параметрам вище. Сервіс сам дістане з нього `id`, `fn`, `sm` і склеїть `date` з окремих `date` та `time`. Якщо параметр передано разом із явними — явні мають перевагу.\n\nРозпізнаються обидва варіанти, які трапляються на практиці: числовий `id` з `time=1431` і рядковий `id` з `time=12%3A57%3A37`. Пробіл-роздільник тисяч у сумі (зокрема `%20` та нерозривний) прибирається, кома як дробовий роздільник замінюється крапкою. Зайві параметри на кшталт `mac` ігноруються.",
						"schema": {
							"type": "string"
						},
						"example": "https://cabinet.tax.gov.ua/cashregs/check?id=PDgeh4_KHik&date=20260910&time=12%3A57%3A37&fn=4001127837&sm=471.00"
					},
					{
						"name": "id",
						"in": "query",
						"required": false,
						"description": "Ідентифікатор чека — фіскальний номер документа. У QR-коді чека це параметр `id`. Обовʼязковий, якщо не передано `url`.",
						"schema": {
							"type": "string"
						},
						"example": "2037032"
					},
					{
						"name": "date",
						"in": "query",
						"required": false,
						"description": "Дата й час чека у форматі `YYYY-MM-DD HH:MM:SS`. Береться з чека; секунди зазвичай нульові. Значення потребує URL-кодування (пробіл → `%20`). Обовʼязковий, якщо не передано `url`.",
						"schema": {
							"type": "string",
							"pattern": "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$"
						},
						"example": "2026-09-10 09:25:00"
					},
					{
						"name": "fn",
						"in": "query",
						"required": false,
						"description": "Фіскальний номер РРО або ПРРО, на якому видано чек — 10 цифр. Обовʼязковий, якщо не передано `url`.",
						"schema": {
							"type": "string"
						},
						"example": "3001164740"
					},
					{
						"name": "sm",
						"in": "query",
						"required": false,
						"description": "Загальна сума чека в гривнях. Роздільник дробової частини — крапка. Обовʼязковий, якщо не передано `url`.",
						"schema": {
							"type": "string"
						},
						"example": "270.0"
					},
					{
						"name": "forced",
						"in": "query",
						"required": false,
						"description": "Передайте `1`, щоб ігнорувати кеш і перезапитати реєстр. Таке звернення завжди списує баланс. Будь-яке інше значення або відсутність параметра означає звичайний режим із кешем.",
						"schema": {
							"type": "string",
							"enum": [
								"1"
							]
						},
						"example": "1"
					},
					{
						"name": "printed",
						"in": "query",
						"required": false,
						"description": "Передайте `0`, щоб не повертати текстове представлення чека.",
						"schema": {
							"type": "string",
							"enum": [
								"0"
							]
						},
						"example": "0"
					},
					{
						"name": "raw",
						"in": "query",
						"required": false,
						"description": "Передайте `1`, щоб додати до відповіді сирий payload реєстру — так, як його віддає `/cheque_info`.",
						"schema": {
							"type": "string",
							"enum": [
								"1"
							]
						},
						"example": "1"
					}
				],
				"responses": {
					"200": {
						"description": "Запит оброблено.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ChequeStructuredResponse"
								}
							}
						}
					},
					"401": {
						"$ref": "#/components/responses/Unauthorized"
					}
				}
			}
		},
		"/get_balance": {
			"post": {
				"tags": [
					"Баланс"
				],
				"operationId": "getBalance",
				"summary": "Залишок запитів",
				"description": "Повертає залишок запитів у поточному відкритому поповненні. Тіло запиту не потрібне.",
				"responses": {
					"200": {
						"description": "Поточний баланс",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/BalanceResponse"
								},
								"examples": {
									"ok": {
										"summary": "Звичайна відповідь",
										"value": {
											"success": true,
											"errorMessage": null,
											"message": null,
											"balance": 99998.5
										}
									}
								}
							}
						}
					},
					"401": {
						"$ref": "#/components/responses/Unauthorized"
					}
				}
			}
		},
		"/cheque_photo": {
			"post": {
				"tags": [
					"Чеки"
				],
				"operationId": "chequePhoto",
				"summary": "Перевірка чека за фотографією",
				"description": "Приймає фото чека й повертає розібраний документ.\n\n**Як визначаються реквізити.** Спершу беруться ті, що передані явно (`id`, `date`, `fn`, `sm`) — вони мають перевагу над усім іншим. Далі розбирається переданий рядок QR-коду (`url`). Якщо реквізитів усе ще бракує, QR шукається на самій фотографії. І лише коли й це не дало результату, фото читає AI-агент, налаштований у панелі.\n\nКоли QR-код дав повний набір реквізитів, до AI не звертаємося взагалі — посилання на кабінет ДПС саме по собі підтверджує, що чек фіскальний.\n\n**Нефіскальні документи.** Товарного чека чи роздруківки з облікової програми в реєстрі ДПС немає за визначенням. Для них запит до реєстру не виконується, а дані беруться з самої фотографії — відповідь має ту саму структуру, лише `source` дорівнює `photo`, а `cheque.sourceFormat` — `AI`.\n\n**Списання балансу.** Розпізнавання фото безкоштовне; списується лише звернення до реєстру за звичайними правилами (знайдений чек — 1, ненайдений — 0,5, відповідь із кешу — безкоштовно).\n\n**Формати запиту.** `multipart/form-data` з файлом у полі `image`, або будь-який формат із посиланням `image_url`, або тіло JSON із `imageUrl`. Максимальний розмір файлу — 100 МБ. Великий знімок перед надсиланням до AI автоматично зменшується, тож на швидкість і вартість розпізнавання це майже не впливає.",
				"requestBody": {
					"required": true,
					"content": {
						"multipart/form-data": {
							"schema": {
								"type": "object",
								"properties": {
									"image": {
										"type": "string",
										"format": "binary",
										"description": "Фото чека. Потрібне або це поле, або image_url. Приймаються також назви частини photo, file, cheque."
									},
									"image_url": {
										"type": "string",
										"description": "Посилання на фото — замість файлу. Лише http і https, без перенаправлень, не на внутрішні адреси.",
										"example": "https://storage.example.com/cheques/1.jpg"
									},
									"url": {
										"type": "string",
										"description": "Уже прочитаний рядок QR-коду, якщо він у вас є.",
										"example": "https://cabinet.tax.gov.ua/cashregs/check?id=…"
									},
									"id": {
										"type": "string",
										"description": "Фіскальний номер чека, якщо відомий."
									},
									"date": {
										"type": "string",
										"description": "Дата й час, YYYY-MM-DD HH:MM:SS."
									},
									"fn": {
										"type": "string",
										"description": "Фіскальний номер каси, 10 цифр."
									},
									"sm": {
										"type": "string",
										"description": "Сума чека, роздільник — крапка."
									},
									"forced": {
										"type": "string",
										"enum": [
											"0",
											"1"
										],
										"description": "1 — не брати відповідь із кешу."
									}
								}
							}
						},
						"application/json": {
							"schema": {
								"type": "object",
								"required": [
									"imageUrl"
								],
								"properties": {
									"imageUrl": {
										"type": "string",
										"description": "Посилання на фото чека."
									},
									"url": {
										"type": "string",
										"description": "Рядок QR-коду, якщо він у вас є."
									},
									"id": {
										"type": "string"
									},
									"date": {
										"type": "string"
									},
									"fn": {
										"type": "string"
									},
									"sm": {
										"type": "string"
									},
									"forced": {
										"type": "string",
										"enum": [
											"0",
											"1"
										]
									}
								}
							},
							"example": {
								"imageUrl": "https://storage.example.com/cheques/1.jpg"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Запит оброблено.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ChequePhotoResponse"
								}
							}
						}
					},
					"400": {
						"description": "Фото не передано або його не вдалося завантажити.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/BasicApiPacket"
								}
							}
						}
					},
					"401": {
						"$ref": "#/components/responses/Unauthorized"
					}
				}
			}
		}
	},
	"components": {
		"securitySchemes": {
			"basicAuth": {
				"type": "http",
				"scheme": "basic",
				"description": "HTTP Basic. Логін і пароль — ті самі, що й для панелі адміністратора. Заголовок: `Authorization: Basic base64(логін:пароль)`. Без нього або з невірними даними сервіс відповідає `401` з порожнім тілом."
			}
		},
		"responses": {
			"Unauthorized": {
				"description": "Відсутній, некоректний або невірний заголовок `Authorization`. Тіло відповіді порожнє."
			}
		},
		"schemas": {
			"BasicApiPacket": {
				"type": "object",
				"properties": {
					"success": {
						"type": "boolean",
						"description": "Чи опрацьовано запит сервісом. `false` означає проблему на боці ChequeChecker (немає балансу, бракує параметра, помилка бази) — а не те, що чек не знайдено."
					},
					"errorMessage": {
						"type": "string",
						"nullable": true,
						"description": "Текст помилки сервісу ChequeChecker. `null`, коли `success: true`."
					},
					"message": {
						"type": "string",
						"nullable": true,
						"description": "Зарезервовано для інформаційних повідомлень. Зазвичай `null`."
					}
				}
			},
			"ChequeInfoResponse": {
				"allOf": [
					{
						"$ref": "#/components/schemas/BasicApiPacket"
					},
					{
						"type": "object",
						"properties": {
							"fromDFS": {
								"type": "boolean",
								"description": "`true` — відповідь щойно отримана з реєстру ДПС, з балансу списано. `false` — віддано з локального кешу, баланс не змінився."
							},
							"error": {
								"type": "string",
								"nullable": true,
								"description": "Код помилки від реєстру ДПС. `null` або порожній рядок означає, що чек знайдено."
							},
							"error_description": {
								"type": "string",
								"nullable": true,
								"description": "Текст помилки від реєстру ДПС, наприклад «Інформація відсутня Не знайдено.» або «Інформація відсутня Сервіс тимчасово недоступний .»"
							},
							"check": {
								"type": "string",
								"nullable": true,
								"description": "Текстове представлення чека, закодоване в base64. Після декодування — звичайний текст у UTF-8, придатний для друку моноширинним шрифтом."
							},
							"checkXml": {
								"type": "string",
								"nullable": true,
								"description": "Структуроване XML-представлення чека від реєстру: реквізити продавця, позиції, суми, податки."
							}
						}
					}
				]
			},
			"BalanceResponse": {
				"allOf": [
					{
						"$ref": "#/components/schemas/BasicApiPacket"
					},
					{
						"type": "object",
						"properties": {
							"balance": {
								"type": "number",
								"format": "double",
								"description": "Залишок запитів у відкритому поповненні. Знайдений чек списує 1, помилка реєстру — 0,5, відповідь із кешу — 0. Значення може бути дробовим."
							}
						}
					}
				]
			},
			"ChequeStructuredResponse": {
				"type": "object",
				"properties": {
					"success": {
						"type": "boolean",
						"description": "Чи опрацьовано запит сервісом."
					},
					"found": {
						"type": "boolean",
						"description": "Чи знайдено чек у реєстрі."
					},
					"fromCache": {
						"type": "boolean",
						"description": "`true` — відповідь із локального кешу, баланс не списано."
					},
					"error": {
						"type": "string",
						"nullable": true,
						"description": "Код помилки від реєстру, якщо чек не знайдено."
					},
					"errorDescription": {
						"type": "string",
						"nullable": true,
						"description": "Текст помилки від реєстру."
					},
					"parseFailed": {
						"type": "boolean",
						"nullable": true,
						"description": "Присутнє лише тоді, коли чек знайдено, але розібрати його не вдалося."
					},
					"printed": {
						"type": "string",
						"nullable": true,
						"description": "Текстове представлення чека, вже розкодоване з base64."
					},
					"raw": {
						"type": "string",
						"nullable": true,
						"description": "Сира відповідь реєстру. Лише при `raw=1`."
					},
					"cheque": {
						"$ref": "#/components/schemas/Cheque"
					}
				}
			},
			"Cheque": {
				"type": "object",
				"description": "Нормалізований чек. Суми у гривнях незалежно від того, як їх передав реєстр.",
				"properties": {
					"sourceFormat": {
						"type": "string",
						"description": "Формат, з якого розібрано документ: RQ і CHECK — два діалекти відповіді реєстру, AI — дані прочитані з фотографії."
					},
					"parserVersion": {
						"type": "integer",
						"description": "Версія логіки розбору."
					},
					"document": {
						"$ref": "#/components/schemas/ChequeDocument"
					},
					"seller": {
						"$ref": "#/components/schemas/ChequeSeller"
					},
					"totals": {
						"$ref": "#/components/schemas/ChequeTotals"
					},
					"items": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/ChequeItem"
						}
					},
					"payments": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/ChequePayment"
						}
					},
					"taxes": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/ChequeTax"
						}
					},
					"docKind": {
						"type": "string",
						"nullable": true,
						"enum": [
							"fiscal",
							"printed",
							"accounting",
							"non_fiscal",
							"unknown"
						],
						"description": "Вид документа, визначений за фотографією:\n* `fiscal` — фіскальний чек РРО або ПРРО;\n* `printed` — друкований товарний чек;\n* `accounting` — роздруківка з облікової програми;\n* `non_fiscal` — інший нефіскальний документ;\n* `unknown` — визначити не вдалося.\n\nЧек, перевірений без фотографії, вважається фіскальним."
					},
					"docKindSource": {
						"type": "string",
						"nullable": true,
						"enum": [
							"qr",
							"ai"
						],
						"description": "Звідки відомий вид документа."
					}
				}
			},
			"ChequeDocument": {
				"type": "object",
				"properties": {
					"chequeId": {
						"type": "string",
						"nullable": true,
						"description": "Ідентифікатор, за яким шукали чек — параметр `id`."
					},
					"fiscalNumber": {
						"type": "string",
						"nullable": true,
						"description": "Фіскальний номер РРО або ПРРО."
					},
					"factoryNumber": {
						"type": "string",
						"nullable": true,
						"description": "Заводський номер (ЗН)."
					},
					"localNumber": {
						"type": "string",
						"nullable": true,
						"description": "Внутрішній номер каси (ВН)."
					},
					"number": {
						"type": "string",
						"nullable": true,
						"description": "Номер чека в межах РРО."
					},
					"type": {
						"type": "integer",
						"nullable": true,
						"description": "Тип документа: 0 — касовий чек."
					},
					"subtype": {
						"type": "integer",
						"nullable": true,
						"description": "Підтип документа. Лише у форматі `CHECK`."
					},
					"uid": {
						"type": "string",
						"nullable": true,
						"description": "Унікальний ідентифікатор документа. Лише у форматі `CHECK`."
					},
					"offline": {
						"type": "boolean",
						"nullable": true,
						"description": "Чек створено в офлайн-режимі."
					},
					"testing": {
						"type": "boolean",
						"nullable": true,
						"description": "Тестовий документ."
					},
					"previousHash": {
						"type": "string",
						"nullable": true,
						"description": "Хеш попереднього документа в офлайн-ланцюжку."
					},
					"issuedAt": {
						"type": "string",
						"nullable": true,
						"description": "Дата й час чека, ISO-8601 без зони: реєстр зсуву не повідомляє."
					}
				}
			},
			"ChequeSeller": {
				"type": "object",
				"properties": {
					"tin": {
						"type": "string",
						"nullable": true,
						"description": "ЄДРПОУ для юрособи або ІД для ФОП."
					},
					"ipn": {
						"type": "string",
						"nullable": true,
						"description": "Податковий номер, 12 цифр."
					},
					"name": {
						"type": "string",
						"nullable": true,
						"description": "Назва продавця."
					},
					"pointName": {
						"type": "string",
						"nullable": true,
						"description": "Назва точки продажу."
					},
					"pointAddress": {
						"type": "string",
						"nullable": true,
						"description": "Адреса."
					},
					"cashier": {
						"type": "string",
						"nullable": true,
						"description": "Касир."
					},
					"source": {
						"type": "string",
						"nullable": true,
						"description": "Звідки взято назву й адресу: `xml`, `certificate` або `printed`."
					}
				}
			},
			"ChequeTotals": {
				"type": "object",
				"properties": {
					"sum": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Сума до сплати."
					},
					"sumNoRound": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Сума без заокруглення."
					},
					"roundSum": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Сума заокруглення."
					},
					"discountSum": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Загальна знижка."
					},
					"currency": {
						"type": "string",
						"nullable": true,
						"description": "Валюта, якщо не гривня."
					}
				}
			},
			"ChequeItem": {
				"type": "object",
				"properties": {
					"rowNum": {
						"type": "integer",
						"nullable": true,
						"description": "Номер позиції в чеку."
					},
					"code": {
						"type": "string",
						"nullable": true,
						"description": "Артикул продавця."
					},
					"barcode": {
						"type": "string",
						"nullable": true,
						"description": "Штрихкод."
					},
					"uktzed": {
						"type": "string",
						"nullable": true,
						"description": "Код УКТЗЕД. У форматі `RQ` часом захований на початку назви — сервіс його звідти дістає."
					},
					"name": {
						"type": "string",
						"nullable": true,
						"description": "Назва товару, без коду УКТЗЕД на початку."
					},
					"unitCode": {
						"type": "string",
						"nullable": true,
						"description": "Код одиниці виміру."
					},
					"unitName": {
						"type": "string",
						"nullable": true,
						"description": "Одиниця виміру."
					},
					"quantity": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Кількість."
					},
					"price": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Ціна за одиницю."
					},
					"cost": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Вартість позиції."
					},
					"discountSum": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Знижка на позицію."
					},
					"taxLetters": {
						"type": "string",
						"nullable": true,
						"description": "Літери податкових груп."
					},
					"taxGroup": {
						"type": "integer",
						"nullable": true,
						"description": "Номер податкової групи; повʼязує позицію з елементом `taxes` у форматі `RQ`."
					},
					"exciseLabels": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Акцизні марки."
					}
				}
			},
			"ChequePayment": {
				"type": "object",
				"properties": {
					"rowNum": {
						"type": "integer",
						"nullable": true,
						"description": "Номер рядка оплати."
					},
					"formCode": {
						"type": "integer",
						"nullable": true,
						"description": "Код форми оплати: 0 — готівка, 1 — картка."
					},
					"formName": {
						"type": "string",
						"nullable": true,
						"description": "Назва форми оплати."
					},
					"amount": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Сума оплати."
					},
					"provided": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Внесено."
					},
					"remains": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Решта."
					},
					"paySystem": {
						"type": "string",
						"nullable": true,
						"description": "Платіжна система: Visa, MasterCard тощо."
					},
					"acquirer": {
						"type": "string",
						"nullable": true,
						"description": "Еквайр."
					},
					"terminal": {
						"type": "string",
						"nullable": true,
						"description": "Ідентифікатор термінала."
					},
					"cardMasked": {
						"type": "string",
						"nullable": true,
						"description": "Маскований номер картки."
					},
					"authCode": {
						"type": "string",
						"nullable": true,
						"description": "Код авторизації."
					},
					"transactionId": {
						"type": "string",
						"nullable": true,
						"description": "Ідентифікатор транзакції (RRN)."
					},
					"commission": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Комісія."
					}
				}
			},
			"ChequeTax": {
				"type": "object",
				"properties": {
					"rowNum": {
						"type": "integer",
						"nullable": true,
						"description": "Номер рядка."
					},
					"groupIndex": {
						"type": "integer",
						"nullable": true,
						"description": "Номер податкової групи у форматі `RQ`."
					},
					"type": {
						"type": "integer",
						"nullable": true,
						"description": "Тип податку."
					},
					"name": {
						"type": "string",
						"nullable": true,
						"description": "Назва податку."
					},
					"letter": {
						"type": "string",
						"nullable": true,
						"description": "Літера податкової групи."
					},
					"percent": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Ставка, відсотки."
					},
					"turnover": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Оборот. Лише у форматі `CHECK`."
					},
					"sourceSum": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Сума без податку. Лише у форматі `CHECK`."
					},
					"amount": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Сума податку."
					}
				}
			},
			"ChequePhotoResponse": {
				"type": "object",
				"properties": {
					"success": {
						"type": "boolean",
						"description": "false — запит виконати не вдалося; причина в errorMessage."
					},
					"found": {
						"type": "boolean",
						"description": "Чи є що показати: чек знайдено в реєстрі або прочитано з фото."
					},
					"kind": {
						"type": "string",
						"enum": [
							"fiscal",
							"printed",
							"accounting",
							"non_fiscal",
							"unknown"
						],
						"description": "Вид документа, визначений за фотографією:\n* `fiscal` — фіскальний чек РРО або ПРРО;\n* `printed` — друкований товарний чек;\n* `accounting` — роздруківка з облікової програми;\n* `non_fiscal` — інший нефіскальний документ;\n* `unknown` — визначити не вдалося.\n\nЧек, перевірений без фотографії, вважається фіскальним."
					},
					"kindSource": {
						"type": "string",
						"enum": [
							"qr",
							"ai"
						],
						"nullable": true,
						"description": "Звідки відомий вид: QR-код на фото чи висновок AI."
					},
					"kindConfidence": {
						"type": "number",
						"format": "double",
						"nullable": true,
						"description": "Впевненість моделі від 0 до 1."
					},
					"kindReason": {
						"type": "string",
						"nullable": true,
						"description": "Коротке пояснення моделі, чому саме такий вид."
					},
					"fiscal": {
						"type": "boolean",
						"description": "true — документ шукали в реєстрі ДПС."
					},
					"source": {
						"type": "string",
						"enum": [
							"registry",
							"photo"
						],
						"nullable": true,
						"description": "Звідки взято дані чека."
					},
					"fromCache": {
						"type": "boolean",
						"description": "true — відповідь реєстру взято з кешу, списання не було."
					},
					"resolved": {
						"type": "object",
						"description": "Реквізити, з якими насправді пішли в реєстр.",
						"properties": {
							"id": {
								"type": "string",
								"nullable": true
							},
							"date": {
								"type": "string",
								"nullable": true
							},
							"fn": {
								"type": "string",
								"nullable": true
							},
							"sm": {
								"type": "string",
								"nullable": true
							},
							"from": {
								"type": "string",
								"enum": [
									"params",
									"qr",
									"photo-qr",
									"ai",
									"mixed"
								],
								"nullable": true,
								"description": "Джерело реквізитів."
							}
						}
					},
					"qr": {
						"type": "object",
						"properties": {
							"found": {
								"type": "boolean"
							},
							"text": {
								"type": "string",
								"nullable": true,
								"description": "Вміст QR-коду, якщо його прочитали."
							}
						}
					},
					"ai": {
						"type": "object",
						"nullable": true,
						"description": "Заповнене, лише якщо до моделі зверталися.",
						"properties": {
							"provider": {
								"type": "string",
								"enum": [
									"gemini",
									"openai"
								]
							},
							"model": {
								"type": "string"
							},
							"millis": {
								"type": "integer",
								"format": "int64"
							},
							"error": {
								"type": "string",
								"nullable": true
							}
						}
					},
					"error": {
						"type": "string",
						"nullable": true,
						"description": "Код помилки від реєстру."
					},
					"errorDescription": {
						"type": "string",
						"nullable": true
					},
					"cheque": {
						"allOf": [
							{
								"$ref": "#/components/schemas/Cheque"
							}
						],
						"nullable": true,
						"description": "Розібраний чек. Для нефіскального документа зібраний із фотографії — тоді sourceFormat дорівнює AI."
					},
					"recognitionId": {
						"type": "integer",
						"format": "int64",
						"description": "Номер розпізнавання в журналі панелі."
					},
					"message": {
						"type": "string",
						"nullable": true,
						"description": "Пояснення, коли зібрати дані не вдалося."
					},
					"errorMessage": {
						"type": "string",
						"nullable": true
					}
				}
			}
		}
	}
}
