{
  "openapi": "3.1.0",
  "info": {
    "title": "rombik API",
    "version": "1.0.0",
    "description": "Генерація блок-схем алгоритмів з коду за ДСТУ. Той самий рушій, що й на сайті rombik, по HTTP. Авторизація — API-ключем (rk_…) у заголовку X-API-Key або Authorization: Bearer. /render списує 1 кредит за кожну схему."
  },
  "servers": [{ "url": "https://rombik.app/api/v1" }],
  "x-rate-limit": "Платний /render — без ліміту (обмежений кредитами). Безкоштовні (/me, /topup, /products, /gift) — 60 запитів/хв на ключ; при перевищенні — 429 із заголовком Retry-After.",
  "security": [{ "ApiKeyAuth": [] }, { "BearerAuth": [] }],
  "paths": {
    "/render": {
      "post": {
        "summary": "Рендер блок-схеми з коду",
        "description": "Код → блок-схема у вибраному форматі. Списує 1 кредит за кожну схему (1 схема — 1 кредит). Тіло — JSON {code,lang,…} або multipart-файл (-F file=@…). Типово повертає файл; з ?json=1 — JSON із полем content.",
        "parameters": [
          { "name": "json", "in": "query", "required": false, "schema": { "type": "boolean" }, "description": "Повернути JSON {content} замість файлу" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/RenderRequest" } },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": { "type": "string", "format": "binary", "description": "Файл вихідного коду (мова — за розширенням)" },
                  "format": { "type": "string" },
                  "lang": { "type": "string" },
                  "fn": { "type": "string" },
                  "scale": { "type": "integer" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Схема — файл або JSON (за ?json=1)",
            "content": {
              "image/svg+xml": { "schema": { "type": "string", "format": "binary" } },
              "image/png": { "schema": { "type": "string", "format": "binary" } },
              "application/pdf": { "schema": { "type": "string", "format": "binary" } },
              "application/json": { "schema": { "$ref": "#/components/schemas/RenderJSONResponse" } }
            }
          },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "402": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/render/batch": {
      "post": {
        "summary": "Пакетний рендер кількох джерел за один виклик",
        "description": "Багато елементів (code або url) → один результат. Списує 1 кредит за КОЖНУ схему серед успішних елементів; передумова — на балансі щонайменше за кількістю елементів (1..100). format=pdf (і не bundle=zip) → один багатосторінковий PDF (сторінка на елемент); інші формати → zip із файлом на елемент. З ?json=1 — звіт по кожному елементу + bundle у base64; інакше — файл (pdf|zip) із заголовками X-Rombik-Rendered та X-Rombik-Failed.",
        "parameters": [
          { "name": "json", "in": "query", "required": false, "schema": { "type": "boolean" }, "description": "Повернути JSON-звіт {items,content} замість файлу" }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchRequest" } } }
        },
        "responses": {
          "200": {
            "description": "Bundle (pdf|zip) або JSON-звіт (за ?json=1)",
            "content": {
              "application/pdf": { "schema": { "type": "string", "format": "binary" } },
              "application/zip": { "schema": { "type": "string", "format": "binary" } },
              "application/json": { "schema": { "$ref": "#/components/schemas/BatchResponse" } }
            }
          },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "402": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/me": {
      "get": {
        "summary": "Баланс акаунта за ключем",
        "responses": {
          "200": {
            "description": "Баланс і статус Pro",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Me" } } }
          },
          "401": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/topup": {
      "post": {
        "summary": "Поповнення балансу — лінк на оплату",
        "description": "Створює pending-платіж і повертає посилання на банку монобанку + код-коментар. Тіло: {kind, id|qty}. kind=credits_unit + qty — поштучно; kind=credits + id — пакет; kind=pro + id — рівень Pro.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TopupRequest" } } }
        },
        "responses": {
          "200": { "description": "Лінк на оплату", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TopupResponse" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/gift": {
      "post": {
        "summary": "Подарувати кредити експорту другові за email",
        "description": "Списує qty кредитів з вашого балансу і нараховує їх на акаунт отримувача (створює його за email, якщо акаунта ще нема — кредити чекатимуть на першому вході). Отримувач отримує сповіщення в акаунті та лист. Тіло: {email, qty}.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GiftRequest" } } }
        },
        "responses": {
          "200": { "description": "Подарунок надіслано; повертає новий баланс відправника", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GiftResponse" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/products": {
      "get": {
        "summary": "Каталог цін: пакети кредитів, рівні Pro, поштучна ціна",
        "responses": {
          "200": { "description": "Каталог цін", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Products" } } } },
          "401": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/openapi.json": {
      "get": { "summary": "Ця специфікація", "security": [], "responses": { "200": { "description": "OpenAPI-документ" } } }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-API-Key" },
      "BearerAuth": { "type": "http", "scheme": "bearer" }
    },
    "responses": {
      "Error": {
        "description": "Помилка",
        "content": { "application/json": { "schema": { "type": "object", "properties": {
          "error": { "type": "string", "description": "людський опис" },
          "code": { "type": "string", "description": "машиночитаний код (стабільний; гілкуйся по ньому, не по тексту): unauthorized, no_credits, pro_required, unknown_format, unknown_lang, bad_request, bad_source, render_failed, bad_email, bad_qty, gift_failed, rate_limited, payment_unavailable, server_error", "enum": ["unauthorized", "no_credits", "pro_required", "unknown_format", "unknown_lang", "bad_request", "bad_source", "render_failed", "bad_email", "bad_qty", "gift_failed", "rate_limited", "payment_unavailable", "server_error"] }
        } } } }
      }
    },
    "schemas": {
      "RenderRequest": {
        "type": "object",
        "required": ["lang"],
        "description": "Потрібно або code, або url (взаємовиключно).",
        "properties": {
          "code": { "type": "string", "description": "Вихідний код програми (або задай url)" },
          "url": { "type": "string", "format": "uri", "description": "Завантажити код за посиланням замість code. Дозволені хости: raw.githubusercontent.com, gist.githubusercontent.com, gitlab.com, bitbucket.org, codeberg.org (github.com/.../blob/... авто-нормалізується в raw). Мова — за розширенням у URL, якщо lang не заданий." },
          "lang": { "type": "string", "enum": ["python", "cpp", "c", "java", "csharp", "pascal"] },
          "format": { "type": "string", "enum": ["docx", "typst", "excalidraw", "svg", "png", "pdf", "json"], "default": "svg" },
          "split": { "type": "boolean", "default": true, "description": "розбивати високі схеми на частини конекторами А/Б; дефолт true для docx, pdf (багатосторінковий) і Typst-документа, явне true вмикає й для svg/png/excalidraw (false — суцільна, один аркуш)" },
          "fn": { "type": "string", "description": "Рендерити лише функцію з цим іменем" },
          "scale": { "type": "integer", "description": "PNG: масштаб (zoom), типово 3" },
          "fragment": { "type": "boolean", "description": "Typst: фрагмент CeTZ без преамбули" },
          "font": { "type": "string", "description": "Шрифт SVG (з allowlist)" },
          "json": { "type": "boolean", "description": "Відповідь JSON замість файлу" },
          "options": { "$ref": "#/components/schemas/Options" }
        }
      },
      "Options": {
        "type": "object",
        "description": "Опції рушія (усі необов'язкові; порожні → ДСТУ-замовчування). Кастомні текстові значення (свої підписи гілок, слова вводу/виводу, термінатори, returnWord, forEachWord, capWord, capFormat, forFormat) потребують активного Pro на акаунті — інакше 402 pro_required. Тумблери, locale, font, scale, figStart — безкоштовні.",
        "properties": {
          "locale": { "type": "string", "enum": ["uk", "en"], "default": "uk" },
          "forFormat": { "type": "string", "enum": ["comma", "range", "verbose"] },
          "singleEnd": { "type": "boolean" },
          "mainOnlyTerminators": { "type": "boolean" },
          "callAsProcess": { "type": "boolean" },
          "stripTypes": { "type": "boolean" },
          "returnAsIO": { "type": "boolean" },
          "yes": { "type": "string" }, "no": { "type": "string" },
          "inWord": { "type": "string" }, "outWord": { "type": "string" },
          "startText": { "type": "string" }, "endText": { "type": "string" },
          "entryText": { "type": "string" }, "exitText": { "type": "string" },
          "returnWord": { "type": "string" }, "forEachWord": { "type": "string" },
          "capWord": { "type": "string" }, "capFormat": { "type": "string" },
          "figStart": { "type": "integer" }
        }
      },
      "RenderJSONResponse": {
        "type": "object",
        "properties": {
          "format": { "type": "string" },
          "lang": { "type": "string" },
          "filename": { "type": "string" },
          "encoding": { "type": "string", "enum": ["utf-8", "base64"] },
          "content": { "type": "string", "description": "SVG/Typst/Excalidraw — рядок; PNG/PDF — base64" },
          "creditsLeft": { "type": "integer" }
        }
      },
      "BatchItem": {
        "type": "object",
        "description": "Одне джерело. Потрібно code або url.",
        "properties": {
          "code": { "type": "string", "description": "Вихідний код (або url)" },
          "url": { "type": "string", "format": "uri", "description": "Код за посиланням (ті самі allowlist-хости, що й у /render)" },
          "lang": { "type": "string", "enum": ["python", "cpp", "c", "java", "csharp", "pascal"], "description": "Опційно; для url вгадується за розширенням" },
          "fn": { "type": "string", "description": "Рендерити лише функцію з цим іменем" },
          "name": { "type": "string", "description": "Імʼя файлу в zip (інакше rombik-N)" }
        }
      },
      "BatchRequest": {
        "type": "object",
        "required": ["items"],
        "properties": {
          "items": { "type": "array", "minItems": 1, "maxItems": 100, "items": { "$ref": "#/components/schemas/BatchItem" } },
          "format": { "type": "string", "enum": ["svg", "png", "pdf", "typst", "excalidraw"], "default": "svg" },
          "bundle": { "type": "string", "enum": ["pdf", "zip"], "description": "Пакування. Типово: pdf для format=pdf, інакше zip. bundle=zip із format=pdf → zip окремих PDF." },
          "scale": { "type": "integer", "description": "PNG: масштаб (zoom)" },
          "fragment": { "type": "boolean", "description": "Typst: фрагмент CeTZ без преамбули" },
          "font": { "type": "string", "description": "Шрифт SVG (з allowlist)" },
          "options": { "$ref": "#/components/schemas/Options" }
        }
      },
      "BatchResponse": {
        "type": "object",
        "description": "Відповідь у режимі ?json=1.",
        "properties": {
          "format": { "type": "string" },
          "bundle": { "type": "string", "enum": ["pdf", "zip"] },
          "count": { "type": "integer", "description": "Скільки елементів надіслано" },
          "rendered": { "type": "integer", "description": "Скільки елементів успішно" },
          "credits": { "type": "integer", "description": "Скільки кредитів списано (= сумі схем серед успішних елементів)" },
          "creditsLeft": { "type": "integer" },
          "filename": { "type": "string" },
          "items": {
            "type": "array",
            "items": { "type": "object", "properties": {
              "index": { "type": "integer" },
              "name": { "type": "string" },
              "ok": { "type": "boolean" },
              "error": { "type": "string", "description": "присутнє лише якщо ok=false" }
            } }
          },
          "encoding": { "type": "string", "enum": ["base64"] },
          "content": { "type": "string", "description": "Bundle (pdf|zip) у base64" }
        }
      },
      "Me": {
        "type": "object",
        "properties": {
          "email": { "type": "string" },
          "name": { "type": "string" },
          "credits": { "type": "integer" },
          "pro": { "type": "boolean" },
          "proUntil": { "type": "string", "description": "RFC3339 або \"\" якщо без Pro" }
        }
      },
      "TopupRequest": {
        "type": "object",
        "required": ["kind"],
        "properties": {
          "kind": { "type": "string", "enum": ["credits_unit", "credits", "pro"] },
          "id": { "type": "integer", "description": "id пакета/рівня (для credits|pro)" },
          "qty": { "type": "integer", "description": "к-сть кредитів поштучно (для credits_unit), 2–200" }
        }
      },
      "TopupResponse": {
        "type": "object",
        "properties": {
          "jarUrl": { "type": "string", "description": "Посилання на банку з готовими сумою+кодом" },
          "comment": { "type": "string", "description": "Код-коментар (на випадок ручного введення)" },
          "label": { "type": "string" },
          "amountKop": { "type": "integer" },
          "amountUah": { "type": "number" }
        }
      },
      "GiftRequest": {
        "type": "object",
        "required": ["email", "qty"],
        "properties": {
          "email": { "type": "string", "format": "email", "description": "Email отримувача" },
          "qty": { "type": "integer", "description": "Скільки кредитів подарувати, 1–1000 (не більше за ваш баланс)" }
        }
      },
      "GiftResponse": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "credits": { "type": "integer", "description": "Ваш новий баланс кредитів після подарунка" }
        }
      },
      "Products": {
        "type": "object",
        "properties": {
          "credits": {
            "type": "array",
            "description": "Пакети кредитів (id — для topup kind=credits)",
            "items": { "type": "object", "properties": {
              "id": { "type": "integer" },
              "qty": { "type": "integer" },
              "uah": { "type": "number" }
            } }
          },
          "pro": {
            "type": "array",
            "description": "Рівні Pro (id — для topup kind=pro)",
            "items": { "type": "object", "properties": {
              "id": { "type": "integer" },
              "bonus": { "type": "integer", "description": "Бонусні кредити в комплекті" },
              "days": { "type": "integer", "description": "Тривалість Pro у днях" },
              "uah": { "type": "number" }
            } }
          },
          "unitUah": { "type": "number", "description": "Ціна одного кредита поштучно (topup kind=credits_unit)" },
          "available": { "type": "boolean", "description": "Чи доступна оплата (false → платіжний провайдер не налаштований)" }
        }
      }
    }
  }
}