{
  "openapi": "3.1.0",
  "info": {
    "title": "rombik API",
    "version": "1.0.0",
    "description": "Generate algorithm flowcharts from code per ДСТУ (GOST 19.701-90). The same engine as on the rombik site, over HTTP. Auth by API key (rk_…) in the X-API-Key or Authorization: Bearer header. /render charges 1 credit per chart."
  },
  "servers": [{ "url": "https://rombik.app/api/v1" }],
  "x-rate-limit": "Paid /render — no limit (bound by credits). Free ones (/me, /topup, /products, /gift) — 60 requests/min per key; over the limit → 429 with a Retry-After header.",
  "security": [{ "ApiKeyAuth": [] }, { "BearerAuth": [] }],
  "paths": {
    "/render": {
      "post": {
        "summary": "Render a flowchart from code",
        "description": "Code → flowchart in the chosen format. Charges 1 credit per chart (1 chart — 1 credit). Body — JSON {code,lang,…} or a multipart file (-F file=@…). Returns a file by default; with ?json=1 — JSON with a content field.",
        "parameters": [
          { "name": "json", "in": "query", "required": false, "schema": { "type": "boolean" }, "description": "Return JSON {content} instead of a file" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/RenderRequest" } },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": { "type": "string", "format": "binary", "description": "Source code file (language — by extension)" },
                  "format": { "type": "string" },
                  "lang": { "type": "string" },
                  "fn": { "type": "string" },
                  "scale": { "type": "integer" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chart — a file or JSON (with ?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": "Batch render of several sources in one call",
        "description": "Many items (code or url) → one result. Charges 1 credit per chart among successful items; precondition — balance ≥ item count at least (1..100). format=pdf (and not bundle=zip) → a single multi-page PDF (one page per item); other formats → a zip with one file per item. With ?json=1 — a per-item report + bundle in base64; otherwise a file (pdf|zip) with X-Rombik-Rendered and X-Rombik-Failed headers.",
        "parameters": [
          { "name": "json", "in": "query", "required": false, "schema": { "type": "boolean" }, "description": "Return a JSON report {items,content} instead of a file" }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchRequest" } } }
        },
        "responses": {
          "200": {
            "description": "Bundle (pdf|zip) or a JSON report (with ?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": "Account balance by key",
        "responses": {
          "200": {
            "description": "Balance and Pro status",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Me" } } }
          },
          "401": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/topup": {
      "post": {
        "summary": "Top up balance — payment link",
        "description": "Creates a pending payment and returns a Monobank jar link + a code comment. Body: {kind, id|qty}. kind=credits_unit + qty — per unit; kind=credits + id — a pack; kind=pro + id — a Pro tier.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TopupRequest" } } }
        },
        "responses": {
          "200": { "description": "Payment link", "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": "Gift export credits to a friend by email",
        "description": "Deducts qty credits from your balance and adds them to the recipient's account (creates it by email if there is none yet — credits wait for first sign-in). The recipient gets an in-account notification and an email. Body: {email, qty}.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GiftRequest" } } }
        },
        "responses": {
          "200": { "description": "Gift sent; returns the sender's new balance", "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": "Price catalog: credit packs, Pro tiers, per-credit price",
        "responses": {
          "200": { "description": "Price catalog", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Products" } } } },
          "401": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/openapi.json": {
      "get": { "summary": "This specification", "security": [], "responses": { "200": { "description": "OpenAPI document" } } }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-API-Key" },
      "BearerAuth": { "type": "http", "scheme": "bearer" }
    },
    "responses": {
      "Error": {
        "description": "Error",
        "content": { "application/json": { "schema": { "type": "object", "properties": {
          "error": { "type": "string", "description": "human-readable message" },
          "code": { "type": "string", "description": "machine-readable code (stable; branch on it, not on text): 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": "Provide either code or url (mutually exclusive).",
        "properties": {
          "code": { "type": "string", "description": "Program source code (or set url)" },
          "url": { "type": "string", "format": "uri", "description": "Fetch code from a link instead of code. Allowed hosts: raw.githubusercontent.com, gist.githubusercontent.com, gitlab.com, bitbucket.org, codeberg.org (github.com/.../blob/... is auto-normalized to raw). Language — by the URL extension if lang is not set." },
          "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": "split tall charts into parts with А/Б connectors; default true for docx, pdf (multi-page) & Typst document, explicit true also enables svg/png/excalidraw (false — keep continuous, one sheet)" },
          "fn": { "type": "string", "description": "Render only the function with this name" },
          "scale": { "type": "integer", "description": "PNG: scale (zoom), default 3" },
          "fragment": { "type": "boolean", "description": "Typst: a CeTZ fragment without the preamble" },
          "font": { "type": "string", "description": "SVG font (from an allowlist)" },
          "json": { "type": "boolean", "description": "JSON response instead of a file" },
          "options": { "$ref": "#/components/schemas/Options" }
        }
      },
      "Options": {
        "type": "object",
        "description": "Engine options (all optional; empty → ДСТУ defaults). Custom text values (your own branch labels, input/output words, terminators, returnWord, forEachWord, capWord, capFormat, forFormat) require active Pro on the account — otherwise 402 pro_required. Toggles, locale, font, scale, figStart are free.",
        "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 — a string; PNG/PDF — base64" },
          "creditsLeft": { "type": "integer" }
        }
      },
      "BatchItem": {
        "type": "object",
        "description": "One source. Provide code or url.",
        "properties": {
          "code": { "type": "string", "description": "Source code (or url)" },
          "url": { "type": "string", "format": "uri", "description": "Code from a link (the same allowlist hosts as /render)" },
          "lang": { "type": "string", "enum": ["python", "cpp", "c", "java", "csharp", "pascal"], "description": "Optional; for url it is inferred from the extension" },
          "fn": { "type": "string", "description": "Render only the function with this name" },
          "name": { "type": "string", "description": "File name in the zip (otherwise 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": "Bundling. Default: pdf for format=pdf, otherwise zip. bundle=zip with format=pdf → a zip of separate PDFs." },
          "scale": { "type": "integer", "description": "PNG: scale (zoom)" },
          "fragment": { "type": "boolean", "description": "Typst: a CeTZ fragment without the preamble" },
          "font": { "type": "string", "description": "SVG font (from an allowlist)" },
          "options": { "$ref": "#/components/schemas/Options" }
        }
      },
      "BatchResponse": {
        "type": "object",
        "description": "Response in ?json=1 mode.",
        "properties": {
          "format": { "type": "string" },
          "bundle": { "type": "string", "enum": ["pdf", "zip"] },
          "count": { "type": "integer", "description": "How many items were submitted" },
          "rendered": { "type": "integer", "description": "How many items succeeded" },
          "credits": { "type": "integer", "description": "How many credits were charged (= sum of schemas across successful items)" },
          "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": "present only if ok=false" }
            } }
          },
          "encoding": { "type": "string", "enum": ["base64"] },
          "content": { "type": "string", "description": "Bundle (pdf|zip) in base64" }
        }
      },
      "Me": {
        "type": "object",
        "properties": {
          "email": { "type": "string" },
          "name": { "type": "string" },
          "credits": { "type": "integer" },
          "pro": { "type": "boolean" },
          "proUntil": { "type": "string", "description": "RFC3339 or \"\" if no Pro" }
        }
      },
      "TopupRequest": {
        "type": "object",
        "required": ["kind"],
        "properties": {
          "kind": { "type": "string", "enum": ["credits_unit", "credits", "pro"] },
          "id": { "type": "integer", "description": "pack/tier id (for credits|pro)" },
          "qty": { "type": "integer", "description": "number of credits per unit (for credits_unit), 2–200" }
        }
      },
      "TopupResponse": {
        "type": "object",
        "properties": {
          "jarUrl": { "type": "string", "description": "Jar link prefilled with amount + code" },
          "comment": { "type": "string", "description": "Code comment (in case of manual entry)" },
          "label": { "type": "string" },
          "amountKop": { "type": "integer" },
          "amountUah": { "type": "number" }
        }
      },
      "GiftRequest": {
        "type": "object",
        "required": ["email", "qty"],
        "properties": {
          "email": { "type": "string", "format": "email", "description": "Recipient email" },
          "qty": { "type": "integer", "description": "How many credits to gift, 1–1000 (no more than your balance)" }
        }
      },
      "GiftResponse": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "credits": { "type": "integer", "description": "Your new credit balance after the gift" }
        }
      },
      "Products": {
        "type": "object",
        "properties": {
          "credits": {
            "type": "array",
            "description": "Credit packs (id — for topup kind=credits)",
            "items": { "type": "object", "properties": {
              "id": { "type": "integer" },
              "qty": { "type": "integer" },
              "uah": { "type": "number" }
            } }
          },
          "pro": {
            "type": "array",
            "description": "Pro tiers (id — for topup kind=pro)",
            "items": { "type": "object", "properties": {
              "id": { "type": "integer" },
              "bonus": { "type": "integer", "description": "Bonus credits included" },
              "days": { "type": "integer", "description": "Pro duration in days" },
              "uah": { "type": "number" }
            } }
          },
          "unitUah": { "type": "number", "description": "Price of one credit per unit (topup kind=credits_unit)" },
          "available": { "type": "boolean", "description": "Whether payment is available (false → payment provider not configured)" }
        }
      }
    }
  }
}