{
    "openapi": "3.1.0",
    "info": {
        "title": "API генератора QR-кодов Ку-ку код",
        "version": "1.0.0",
        "description": "Один POST-запрос принимает список строк и параметры листа, в ответ приходит готовый файл: PDF с сеткой кодов, векторные листы SVG или архив с кодами поштучно. Без регистрации, ключей и оплаты. Документация: https://qqkod.ru/api/",
        "contact": {
            "name": "Ку-ку код",
            "email": "info@qqkod.ru",
            "url": "https://qqkod.ru/api/"
        }
    },
    "servers": [
        {
            "url": "https://qqkod.ru"
        }
    ],
    "paths": {
        "/api/v1/sheets": {
            "post": {
                "operationId": "createSheets",
                "summary": "Собрать лист с кодами и вернуть готовый файл",
                "description": "Тело запроса в формате application/json, размер до 2 МБ. Успешный ответ - сам файл, ошибки приходят в JSON. Лимит: 10 запросов в минуту и 300 в сутки с одного IP.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/SheetsRequest"
                            },
                            "examples": {
                                "pdf": {
                                    "summary": "Лист A4 с тремя кодами в PDF",
                                    "value": {
                                        "lines": [
                                            "https://example.com/1 Стол 1",
                                            "https://example.com/2 Стол 2",
                                            "https://example.com/3 Стол 3"
                                        ],
                                        "output": "pdf"
                                    }
                                },
                                "png": {
                                    "summary": "Коды поштучно в PNG из шаблона ссылки",
                                    "value": {
                                        "lines": ["A-101", "A-102", "A-103"],
                                        "url_template": "https://example.com/item/{value}",
                                        "output": "png"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Готовый файл. Тип содержимого зависит от output и количества кодов.",
                        "headers": {
                            "Content-Disposition": {
                                "description": "Имя файла: qqkod.pdf, qqkod.svg, qqkod.zip, qqkod-svg.zip, qqkod-png.zip или имя одиночного кода с подписью (RFC 5987).",
                                "schema": {
                                    "type": "string"
                                }
                            },
                            "X-QQkod-Total-Sheets": {
                                "description": "Сколько листов в тираже",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "X-QQkod-Grid": {
                                "description": "Сетка листа, колонки x строки",
                                "schema": {
                                    "type": "string"
                                }
                            },
                            "X-QQkod-Per-Sheet": {
                                "description": "Сколько кодов помещается на лист",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "X-QQkod-Items": {
                                "description": "Сколько кодов в тираже",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "X-RateLimit-Limit-Minute": {
                                "description": "Лимит запросов в минуту",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "X-RateLimit-Remaining-Minute": {
                                "description": "Остаток запросов в текущей минуте",
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        },
                        "content": {
                            "application/pdf": {
                                "schema": {
                                    "type": "string",
                                    "format": "binary"
                                }
                            },
                            "image/svg+xml": {
                                "schema": {
                                    "type": "string",
                                    "format": "binary"
                                }
                            },
                            "image/png": {
                                "schema": {
                                    "type": "string",
                                    "format": "binary"
                                }
                            },
                            "application/zip": {
                                "schema": {
                                    "type": "string",
                                    "format": "binary"
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "BAD_JSON: тело не разобралось как JSON или верхний уровень не объект",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ApiError"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "NOT_FOUND: путь внутри /api/v1/ не равен /api/v1/sheets",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ApiError"
                                }
                            }
                        }
                    },
                    "405": {
                        "description": "METHOD_NOT_ALLOWED: метод запроса не POST",
                        "headers": {
                            "Allow": {
                                "schema": {
                                    "type": "string"
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ApiError"
                                }
                            }
                        }
                    },
                    "413": {
                        "description": "PAYLOAD_TOO_LARGE: тело запроса больше 2 МБ. BATCH_LIMIT_EXCEEDED: список длиннее 1500 строк или 256 КБ",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ApiError"
                                }
                            }
                        }
                    },
                    "415": {
                        "description": "UNSUPPORTED_MEDIA_TYPE: заголовок Content-Type без application/json",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ApiError"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "VALIDATION_ERROR: неизвестное поле, ошибка в параметре или в строках списка",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ApiError"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "RATE_LIMITED: превышен лимит запросов с одного IP",
                        "headers": {
                            "Retry-After": {
                                "description": "Через сколько секунд повторять",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "X-RateLimit-Limit-Minute": {
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "X-RateLimit-Remaining-Minute": {
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ApiError"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "INTERNAL_ERROR: все прочее",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ApiError"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "SheetsRequest": {
                "type": "object",
                "additionalProperties": false,
                "required": ["lines"],
                "properties": {
                    "lines": {
                        "description": "Список кодов. Одна строка это один код: значение, дальше через пробел необязательная подпись. Массив строк или строка с переносами.",
                        "oneOf": [
                            {
                                "type": "array",
                                "items": {
                                    "type": "string",
                                    "maxLength": 2048
                                },
                                "minItems": 1,
                                "maxItems": 1500
                            },
                            {
                                "type": "string"
                            }
                        ]
                    },
                    "content_type": {
                        "type": "string",
                        "enum": ["url", "text", "phone", "sms", "wifi", "vcard", "email", "ean13"],
                        "default": "url",
                        "description": "Тип содержимого кода. Формат строк совпадает с формой на сайте."
                    },
                    "url_template": {
                        "type": "string",
                        "maxLength": 300,
                        "description": "Шаблон адреса с {value}: строка списка подставляется на место метки. Работает только при content_type равном url."
                    },
                    "output": {
                        "type": "string",
                        "enum": ["pdf", "svg", "svg_files", "png"],
                        "default": "svg",
                        "description": "Формат ответа. svg это листы с сеткой кодов, svg_files и png отдают каждый код отдельным файлом."
                    },
                    "qr_size_cm": {
                        "type": "number",
                        "default": 2.0,
                        "minimum": 1.0,
                        "maximum": 15.0,
                        "description": "Сторона кода в сантиметрах. У ean13 это ширина штрих-кода: по умолчанию 3.7, от 2.9 до 7.5."
                    },
                    "margin_edge": {
                        "type": "number",
                        "default": 1.7,
                        "minimum": 0.0,
                        "maximum": 9.0,
                        "description": "Поле от края листа в сантиметрах."
                    },
                    "margin_between": {
                        "type": "number",
                        "default": 0.6,
                        "minimum": 0.1,
                        "maximum": 5.0,
                        "description": "Промежуток между соседними кодами в сантиметрах."
                    },
                    "frame_margin": {
                        "type": "number",
                        "default": 0.3,
                        "minimum": 0.0,
                        "maximum": 10.0,
                        "description": "Отступ от кода до рамки в сантиметрах."
                    },
                    "border_width": {
                        "type": "number",
                        "default": 0.2,
                        "minimum": 0.0,
                        "maximum": 2.0,
                        "description": "Толщина рамки в миллиметрах. Ноль убирает рамку совсем."
                    },
                    "border_radius": {
                        "type": "number",
                        "default": 0.8,
                        "minimum": 0.0,
                        "maximum": 5.0,
                        "description": "Скругление углов рамки в миллиметрах."
                    },
                    "qr_color": {
                        "type": "string",
                        "default": "#000000",
                        "description": "Цвет кода в HEX. Код должен быть заметно темнее фона, иначе запрос вернет ошибку контраста."
                    },
                    "bg_color": {
                        "type": "string",
                        "default": "#ffffff",
                        "description": "Цвет фона в HEX."
                    },
                    "bg_transparent": {
                        "type": ["boolean", "integer"],
                        "default": false,
                        "description": "Прозрачный фон вместо цвета. Принимает true и false, а также 1 и 0."
                    },
                    "page_format": {
                        "type": "string",
                        "enum": ["a4", "a3", "letter"],
                        "default": "a4",
                        "description": "Формат листа."
                    },
                    "page_orientation": {
                        "type": "string",
                        "enum": ["portrait", "landscape"],
                        "default": "portrait",
                        "description": "Ориентация листа."
                    }
                }
            },
            "ApiError": {
                "type": "object",
                "required": ["ok", "error", "meta"],
                "properties": {
                    "ok": {
                        "type": "boolean",
                        "const": false
                    },
                    "error": {
                        "type": "object",
                        "required": ["code", "message", "fields"],
                        "properties": {
                            "code": {
                                "type": "string",
                                "enum": [
                                    "NOT_FOUND",
                                    "METHOD_NOT_ALLOWED",
                                    "UNSUPPORTED_MEDIA_TYPE",
                                    "PAYLOAD_TOO_LARGE",
                                    "BAD_JSON",
                                    "VALIDATION_ERROR",
                                    "BATCH_LIMIT_EXCEEDED",
                                    "RATE_LIMITED",
                                    "INTERNAL_ERROR"
                                ]
                            },
                            "message": {
                                "type": "string",
                                "description": "Пояснение на русском"
                            },
                            "fields": {
                                "type": "object",
                                "description": "Ошибки по полям запроса. Построчные ошибки списка приходят под ключом urls.",
                                "additionalProperties": {
                                    "type": "array",
                                    "items": {
                                        "type": "string"
                                    }
                                }
                            },
                            "hint": {
                                "type": "object",
                                "description": "Диагноз провала списка с советом. Приходит только при типовых проблемах.",
                                "required": ["code", "title", "text"],
                                "properties": {
                                    "code": {
                                        "type": "string"
                                    },
                                    "title": {
                                        "type": "string"
                                    },
                                    "text": {
                                        "type": "string"
                                    },
                                    "action": {
                                        "type": "object"
                                    }
                                }
                            }
                        }
                    },
                    "meta": {
                        "type": "object",
                        "required": ["request_id"],
                        "properties": {
                            "request_id": {
                                "type": "string",
                                "description": "Идентификатор запроса, его удобно приложить к письму об ошибке"
                            }
                        }
                    }
                }
            }
        }
    }
}
